[GH-ISSUE #60] kernel: data written through a shared mapping and never msync'd is lost at unmount (UFS, msdosfs) #53

Closed
opened 2026-11-03 22:03:16 -07:00 by gitea-mirror · 1 comment
Owner

Originally created by @raynorpat on GitHub (Oct 16, 2026).
Original GitHub issue: https://github.com/RhapsodiOS/RhapsodiOS/issues/60

Symptom

Data written to a file through a MAP_SHARED mapping and never msynced is lost when the file system is unmounted. After munmap, close, sync, umount and mount, the file reads back as all zeros.

  • Affected: UFS and FAT16 (msdosfs).
  • Kernels: the unmodified issue-43 kernel, and the kernel with the #59 fixes (ffs-perf d6aad145f).

No truncate is involved, so this is separate from #59.

Reproduce

fd = open(path, O_RDWR|O_CREAT|O_TRUNC, 0644);
ftruncate(fd, 64*1024);
p = mmap(NULL, 64*1024, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
fill p with a pattern;            /* no msync */
munmap(p, 64*1024); close(fd);
sync; umount <fs>; mount <fs>     /* then read the file: all zeros */

The same sequence with msync before munmap, or with write() instead of the mapping, keeps the data.

Where to look

Probably the same discard as #59's UFS half, reached at unmount:

  • vnode_pager_umount → vnode_uncache makes the file's VM object non-persistent.
  • When the last reference goes, vm_object_terminate (vm/vm_object.c ~469) frees the object's pages without writing the dirty ones back.
  • sync doesn't push dirty mapped pages that aren't on a pageout queue yet, so they are still only in memory at unmount.

The fix should push dirty pages to the pager (or flush the object) before an uncache or terminate drops them, at unmount, and anywhere else that path is reached.

Impact

Silent data loss for any program that writes through a shared mapping and relies on munmap/close and a clean unmount (or shutdown), rather than an explicit msync, to get the data to disk.

Found while verifying #59. Related: #59, #43 (msync).

Originally created by @raynorpat on GitHub (Oct 16, 2026). Original GitHub issue: https://github.com/RhapsodiOS/RhapsodiOS/issues/60 ## Symptom Data written to a file through a `MAP_SHARED` mapping and never `msync`ed is lost when the file system is unmounted. After `munmap`, `close`, `sync`, `umount` and `mount`, the file reads back as all zeros. - **Affected:** UFS and FAT16 (msdosfs). - **Kernels:** the unmodified issue-43 kernel, and the kernel with the #59 fixes (ffs-perf d6aad145f). No truncate is involved, so this is separate from #59. ## Reproduce ``` fd = open(path, O_RDWR|O_CREAT|O_TRUNC, 0644); ftruncate(fd, 64*1024); p = mmap(NULL, 64*1024, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); fill p with a pattern; /* no msync */ munmap(p, 64*1024); close(fd); sync; umount <fs>; mount <fs> /* then read the file: all zeros */ ``` The same sequence with `msync` before `munmap`, or with `write()` instead of the mapping, keeps the data. ## Where to look Probably the same discard as #59's UFS half, reached at unmount: - `vnode_pager_umount` → `vnode_uncache` makes the file's VM object non-persistent. - When the last reference goes, `vm_object_terminate` (`vm/vm_object.c` ~469) frees the object's pages without writing the dirty ones back. - `sync` doesn't push dirty mapped pages that aren't on a pageout queue yet, so they are still only in memory at unmount. The fix should push dirty pages to the pager (or flush the object) before an uncache or terminate drops them, at unmount, and anywhere else that path is reached. ## Impact Silent data loss for any program that writes through a shared mapping and relies on `munmap`/`close` and a clean unmount (or shutdown), rather than an explicit `msync`, to get the data to disk. Found while verifying #59. Related: #59, #43 (msync).
gitea-mirror 2026-11-03 22:03:16 -07:00
  • closed this issue
  • added the
    bug
    label
Author
Owner

@raynorpat commented on GitHub (Oct 17, 2026):

Fixed in 1f72ab1e9 (issue-60), merged into ffs-perf as 2d59e84a0.

Root cause: vnode_pager_umount → vnode_uncache → vm_object_terminate (vm/vm_object.c) freed a file object's resident pages without writing back the ones dirtied through a shared mapping. Any other terminate of a file object (cache trim, cache clear, unlink's uncache) took the same path.

Fix: before vm_object_terminate waits for paging and frees the pages, it pushes every dirty, non-busy page of a file-backed (non-device, non-swapfile) object through vm_pager_put. This mirrors the msync path in vm_synchronize.c.

Verification (i386 QEMU guest, clean kernel built from the commit, cksum 1203060887 1857496, with the #47 and #59 fixes present):

Test UFS FAT16 ext2 HFS
Issue reproducer: mmap write, no msync, munmap/close, sync, umount, remount PASS PASS PASS PASS
Same write, then sync; /sbin/reboot with no umount, check after boot PASS PASS PASS PASS
stress-mmap shared/mixed/private/trunc PASS PASS PASS PASS
ext2_mmap_truncate_test PASS PASS PASS PASS
  • Before the fix, UFS, FAT16 and HFS all read back zeros; ext2 already passed. An instrumented build showed vm_object_terminate dropping 8 dirty pages per file at unmount, and pushing them with the fix.
  • make check-mmap passes.
  • fsck (UFS) and fsck_msdos (FAT16) are clean after the runs.
<!-- gh-comment-id:6229516547 --> @raynorpat commented on GitHub (Oct 17, 2026): Fixed in 1f72ab1e9 (`issue-60`), merged into `ffs-perf` as 2d59e84a0. **Root cause:** `vnode_pager_umount` → `vnode_uncache` → `vm_object_terminate` (`vm/vm_object.c`) freed a file object's resident pages without writing back the ones dirtied through a shared mapping. Any other terminate of a file object (cache trim, cache clear, unlink's uncache) took the same path. **Fix:** before `vm_object_terminate` waits for paging and frees the pages, it pushes every dirty, non-busy page of a file-backed (non-device, non-swapfile) object through `vm_pager_put`. This mirrors the msync path in `vm_synchronize.c`. **Verification** (i386 QEMU guest, clean kernel built from the commit, cksum `1203060887 1857496`, with the #47 and #59 fixes present): | Test | UFS | FAT16 | ext2 | HFS | |---|---|---|---|---| | Issue reproducer: mmap write, no msync, munmap/close, sync, umount, remount | PASS | PASS | PASS | PASS | | Same write, then `sync; /sbin/reboot` with no umount, check after boot | PASS | PASS | PASS | PASS | | `stress-mmap` shared/mixed/private/trunc | PASS | PASS | PASS | PASS | | `ext2_mmap_truncate_test` | PASS | PASS | PASS | PASS | - Before the fix, UFS, FAT16 and HFS all read back zeros; ext2 already passed. An instrumented build showed `vm_object_terminate` dropping 8 dirty pages per file at unmount, and pushing them with the fix. - `make check-mmap` passes. - `fsck` (UFS) and `fsck_msdos` (FAT16) are clean after the runs.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
github-starred/RhapsodiOS#53
No description provided.