Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
* fuse-overlayfs-1.17

- main: clear SUID/SGID bits on truncate (CVE-2026-52791).
- main: make sure st_ino needs is unique for all inodes on a device.
- man: uniformly apply bold attributes.

* fuse-overlayfs-1.16

- main: fix incorrect directory entries due to unstable iteration
Expand Down
38 changes: 36 additions & 2 deletions src/overlay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,16 @@ fn empty_upper_dir(upper: &OvlLayer, path: &[u8]) -> FsResult<()> {
Ok(())
}

/// Clear SUID/SGID bits on a file descriptor after a content-modifying operation.
fn clear_suid_sgid(fd: RawFd) -> FsResult<()> {
let st = crate::sys::fs::fstat(fd)?;
if st.st_mode & (libc::S_ISUID | libc::S_ISGID) != 0 {
let new_mode = st.st_mode & !(libc::S_ISUID | libc::S_ISGID);
crate::sys::fs::fchmod(fd, new_mode)?;
}
Ok(())
}

/// The main overlay filesystem state.
pub struct OverlayFs {
config: OverlayConfig,
Expand Down Expand Up @@ -1861,6 +1871,12 @@ impl Filesystem for OverlayFs {
reply.error(Errno::from_i32(e.0));
return;
}
if fd >= 0 {
if let Err(e) = clear_suid_sgid(fd) {
reply.error(Errno::from_i32(e.0));
return;
}
}
}

match inner.do_getattr(node_id, &self.config) {
Expand Down Expand Up @@ -2640,6 +2656,11 @@ impl Filesystem for OverlayFs {
match layer.ds.openat(&path, open_flags, 0o700) {
Ok(safe_fd) => {
let owned_fd = safe_fd.into_owned();
if (flags_raw & libc::O_TRUNC) != 0 {
if let Some(notifier) = self.notifier.get() {
let _ = notifier.inval_inode(INodeNo(ino), -1, 0);
}
}
drop(inner);
self.reply_open_maybe_passthrough(ino, owned_fd, reply);
return;
Expand Down Expand Up @@ -2678,6 +2699,11 @@ impl Filesystem for OverlayFs {
match layer.ds.openat(&path, open_flags, 0o700) {
Ok(safe_fd) => {
let owned_fd = safe_fd.into_owned();
if (flags_raw & libc::O_TRUNC) != 0 {
if let Some(notifier) = self.notifier.get() {
let _ = notifier.inval_inode(INodeNo(ino), -1, 0);
}
}
drop(inner);
self.reply_open_maybe_passthrough(ino, owned_fd, reply);
}
Expand Down Expand Up @@ -2763,6 +2789,8 @@ impl Filesystem for OverlayFs {
Ok(n) => {
if let Some(mode) = restore_mode {
let _ = crate::sys::fs::fchmod(raw_fd, mode);
} else {
let _ = clear_suid_sgid(raw_fd);
}
reply.written(n as u32);
}
Expand Down Expand Up @@ -3544,7 +3572,10 @@ impl Filesystem for OverlayFs {
match crate::sys::fs::fallocate(fd.as_raw_fd(), mode, offset as i64, length as i64)
{
Err(e) => reply.error(Errno::from_i32(e.0)),
Ok(()) => reply.ok(),
Ok(()) => {
let _ = clear_suid_sgid(fd.as_raw_fd());
reply.ok();
}
}
}
}
Expand Down Expand Up @@ -3614,7 +3645,10 @@ impl Filesystem for OverlayFs {
capped_len,
) {
Err(e) => reply.error(Errno::from_i32(e.0)),
Ok(n) => reply.written(std::cmp::min(n, u32::MAX as usize) as u32),
Ok(n) => {
let _ = clear_suid_sgid(fd_out.as_raw_fd());
reply.written(std::cmp::min(n, u32::MAX as usize) as u32);
}
}
}

Expand Down
30 changes: 25 additions & 5 deletions tests/fedora-installs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ mkdir lower:1 upper:2 workdir:3 merged

fuse-overlayfs -o 'sync=0,lowerdir=lower\\:1,upperdir=upper\\:2,workdir=workdir\\:3,suid,dev' merged

$CONTAINER_RUNTIME run --rm -v $(pwd)/merged:/merged fedora dnf --use-host-config --installroot /merged --releasever 41 install -y glibc-common gedit
$CONTAINER_RUNTIME run --rm -v $(pwd)/merged:/merged quay.io/fedora/fedora dnf --use-host-config --installroot /merged --releasever 44 install -y glibc-common gedit

umount merged

Expand All @@ -30,6 +30,11 @@ mkdir upper workdir

gcc -static -o suid-test $(dirname $0)/suid-test.c

printf "truncate through open\n" > lower/killpriv-open-trunc
printf "truncate through setattr\n" > lower/killpriv-setattr-trunc
printf "clear on write\n" > lower/killpriv-write
chmod 6777 lower/killpriv-open-trunc lower/killpriv-setattr-trunc lower/killpriv-write

fuse-overlayfs -o sync=0,threaded=1,lowerdir=lower,upperdir=upper,workdir=workdir,suid,dev merged
SUID_TEST=$(pwd)/suid-test
(cd merged; $SUID_TEST)
Expand All @@ -42,15 +47,30 @@ test $(stat -c %h merged/usr) -gt 2
stat -c %A upper/suid | grep s
stat -c %a upper/nosuid | grep -v s

# Test SUID/SGID clearing on truncate (CVE-2026-52791)
: > merged/killpriv-open-trunc
truncate -s 0 merged/killpriv-setattr-trunc
test $(stat -c %s merged/killpriv-open-trunc) -eq 0
test $(stat -c %s merged/killpriv-setattr-trunc) -eq 0
test $(stat -c %a upper/killpriv-open-trunc) = 777
test $(stat -c %a upper/killpriv-setattr-trunc) = 777
test $(stat -c %a merged/killpriv-open-trunc) = 777
test $(stat -c %a merged/killpriv-setattr-trunc) = 777

# Test SUID/SGID clearing on write
dd if=/dev/zero of=merged/killpriv-write bs=1 count=1 conv=notrunc 2>/dev/null
test $(stat -c %a upper/killpriv-write) = 777
test $(stat -c %a merged/killpriv-write) = 777

# Install some big packages
$CONTAINER_RUNTIME run --rm -v $(pwd)/merged:/merged fedora dnf --use-host-config --installroot /merged --releasever 41 install -y emacs texlive
$CONTAINER_RUNTIME run --rm -v $(pwd)/merged:/merged quay.io/fedora/fedora dnf --use-host-config --installroot /merged --releasever 44 install -y emacs texlive

$CONTAINER_RUNTIME run --rm -v $(pwd)/merged:/merged fedora sh -c 'rm /merged/usr/share/glib-2.0/schemas/gschemas.compiled; glib-compile-schemas /merged/usr/share/glib-2.0/schemas/'
$CONTAINER_RUNTIME run --rm -v $(pwd)/merged:/merged quay.io/fedora/fedora sh -c 'rm /merged/usr/share/glib-2.0/schemas/gschemas.compiled; glib-compile-schemas /merged/usr/share/glib-2.0/schemas/'

umount merged
fuse-overlayfs -o sync=0,lowerdir=lower,upperdir=upper,workdir=workdir,suid,dev merged

$CONTAINER_RUNTIME run --rm -v $(pwd)/merged:/merged fedora sh -c 'rm -rf /merged/usr/share/glib-2.0/'
$CONTAINER_RUNTIME run --rm -v $(pwd)/merged:/merged quay.io/fedora/fedora sh -c 'rm -rf /merged/usr/share/glib-2.0/'

tar -c --to-stdout $(pwd)/merged > /dev/null

Expand All @@ -61,7 +81,7 @@ mkdir upper workdir lower
# fast_ino_check
fuse-overlayfs -o fast_ino_check=1,sync=0,lowerdir=lower,upperdir=upper,workdir=workdir,suid,dev merged

$CONTAINER_RUNTIME run --rm -v $(pwd)/merged:/merged fedora dnf --use-host-config --installroot /merged --releasever 41 install -y glibc-common gedit
$CONTAINER_RUNTIME run --rm -v $(pwd)/merged:/merged quay.io/fedora/fedora dnf --use-host-config --installroot /merged --releasever 44 install -y glibc-common gedit

mkdir merged/a-directory

Expand Down
Loading