tdb: Truncate the file after expand failure
authorVolker Lendecke <vl@samba.org>
Wed, 23 Aug 2017 10:48:03 +0000 (12:48 +0200)
committerJeremy Allison <jra@samba.org>
Wed, 23 Aug 2017 23:46:08 +0000 (01:46 +0200)
Without this it's very easy to create virtually huge files: ftruncate expands a
file, the pwrites fail with ENOSPC, thus the write fails. The next writer runs
into the same situation, and ftruncate-expands the file even further. tdb_check
will then spend ages reading the 4GB of zeros byte by byte.

Here we hold the freelist lock or are inside a transaction, so it is safe to
cut the file again. Nobody can have used the space that we have tried to
allocate, so we can't have any stray pointers corrupting the database.

Signed-off-by: Volker Lendecke <vl@samba.org>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
lib/tdb/common/io.c

index 1b422affdca8d59a1d1ba51b369c57f073ba1701..f7a12c34dc9351640abcf36f68ab09df01239a50 100644 (file)
@@ -430,14 +430,14 @@ static int tdb_expand_file(struct tdb_context *tdb, tdb_off_t size, tdb_off_t ad
                        TDB_LOG((tdb, TDB_DEBUG_FATAL, "expand_file write "
                                "returned 0 twice: giving up!\n"));
                        errno = ENOSPC;
-                       return -1;
+                       goto fail;
                }
                if (written == -1) {
                        tdb->ecode = TDB_ERR_OOM;
                        TDB_LOG((tdb, TDB_DEBUG_FATAL, "expand_file write of "
                                 "%u bytes failed (%s)\n", (int)n,
                                 strerror(errno)));
-                       return -1;
+                       goto fail;
                }
                if (written != n) {
                        TDB_LOG((tdb, TDB_DEBUG_WARNING, "expand_file: wrote "
@@ -448,6 +448,29 @@ static int tdb_expand_file(struct tdb_context *tdb, tdb_off_t size, tdb_off_t ad
                size += written;
        }
        return 0;
+
+fail:
+       {
+               int err = errno;
+               int ret;
+
+               /*
+                * We're holding the freelist lock or are inside a
+                * transaction. Cutting the file is safe, the space we
+                * tried to allocate can't have been used anywhere in
+                * the meantime.
+                */
+
+               ret = tdb_ftruncate(tdb, size);
+               if (ret == -1) {
+                       TDB_LOG((tdb, TDB_DEBUG_WARNING, "expand_file: "
+                                "retruncate to %ju failed\n",
+                                (uintmax_t)size));
+               }
+               errno = err;
+       }
+
+       return -1;
 }