Before a truncate, force_delayed_copies maps the file and writes every
page past the new size, so that delayed copies of the data are made
before it is discarded. When the write fault on such a page fails, for
instance because an earlier pager_unlock_page for it found no free
block, libpager answers the fault with memory_object_data_error and the
kernel raises a memory exception in ext2fs itself. diskfs_catch_exception
only covers faults on the disk image, so the exception kills the
translator. Filling the filesystem and truncating a file whose writes
failed is enough to crash it, with or without a journal.
Poke each page with hurd_safe_copyin and hurd_safe_copyout, which catch
the fault, and skip a page that faults. The page lies past the new size
and is discarded anyway. A delayed copy of such a page is then not
forced, so a holder of that copy can see it as zeros; the page could not
be written in the first place.
To reproduce:
- fill an ext2 filesystem until writes fail with ENOSPC,
then truncate one of the files whose writes failed;
the ext2fs translator dies with SIGBUS in poke_pages,
with or without a journal.
With this fix fsck remains clean in such a case with or
without a journal.
---
ext2fs/truncate.c | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/ext2fs/truncate.c b/ext2fs/truncate.c
index 16f852fdb..837fd02c3 100644
--- a/ext2fs/truncate.c
+++ b/ext2fs/truncate.c
@@ -18,6 +18,7 @@
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+#include <hurd/sigpreempt.h>
#include "ext2fs.h"
#ifdef DONT_CACHE_MEMORY_OBJECTS
@@ -227,7 +228,17 @@ poke_pages (memory_object_t obj, vm_offset_t start,
vm_offset_t end)
{
vm_address_t poke;
for (poke = addr; poke < addr + len; poke += vm_page_size)
- *(volatile int *)poke = *(volatile int *)poke;
+ {
+ int word;
+
+ /* A page whose write fault fails, for instance because
+ pager_unlock_page found no free block, raises a memory
+ exception here, and diskfs_catch_exception only covers the
+ disk image. Every poked page lies past the new size and is
+ discarded, so skip it rather than crash. */
+ if (hurd_safe_copyin (&word, (void *) poke, sizeof word) == 0)
+ hurd_safe_copyout ((void *) poke, &word, sizeof word);
+ }
munmap ((caddr_t) addr, len);
}
--
2.56.0