From 869265d6268c19c590005cb25e07cb65b76d608f Mon Sep 17 00:00:00 2001 From: tamnd <1218621+tamnd@users.noreply.github.com> Date: Fri, 13 Nov 2026 00:25:07 +0700 Subject: [PATCH] sockfs: the owner and the mode of the inode of a socket (LTP af_alg07) --- CHANGELOG.md | 4 +++ core/rk-syscall/src/fs.rs | 32 ++++++++++++++++++ core/rk-syscall/src/net.rs | 15 ++++++--- net/rk-net/src/lib.rs | 66 +++++++++++++++++++++++++++++++++++--- net/rk-net/src/tests.rs | 20 ++++++++++++ 5 files changed, 128 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd7ace7..a3e0712 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ This file records each release. The format follows [Keep a Changelog](https://ke ## Unreleased +### Added + +- A socket has the owner and the mode of its inode, as sockfs of Linux. A new socket has the file system user and group IDs of the process and the mode 0777. `fchown`, `fchmod` and `fchownat` with `AT_EMPTY_PATH` change them. After the release of the socket, a change of the owner gives `ENOENT`, as `sockfs_setattr` of Linux for CVE-2019-8912. LTP af_alg07 tests this (#541). + ## [0.5.25] - 2026-11-13 ### Added diff --git a/core/rk-syscall/src/fs.rs b/core/rk-syscall/src/fs.rs index 9093635..9fca438 100644 --- a/core/rk-syscall/src/fs.rs +++ b/core/rk-syscall/src/fs.rs @@ -1727,11 +1727,43 @@ fn set_attr_at( /// calls with `AT_EMPTY_PATH` of Linux, which find the file with a path walk. fn set_attr_fd(cx: &Cx<'_>, fd: u64, raw: bool, attr: &SetAttr, change: Change) -> SysResult { let file = if raw { cx.file(fd)? } else { cx.fdget(fd)? }; + if let Some(sock) = rk_net::SockInode::of(&file) { + return set_attr_sock(cx, file, raw, &sock, attr, change); + } let inode = file.inode().ok_or(Errno::EBADF)?; file.want_write()?; change_attr(cx, file.location(), inode, attr, change) } +/// Changes the attributes of the socket of `file`, as `notify_change` and `sockfs_setattr` of +/// Linux. With `raw`, the call is `AT_EMPTY_PATH` with an empty path. The path lookup of Linux +/// then holds the inode but not the file, so the file goes before the change. If another thread +/// closes the last descriptor of the socket in the meantime, the socket closes, and a change of +/// the owner gives `ENOENT`. LTP af_alg07 checks this race. +fn set_attr_sock( + cx: &Cx<'_>, + file: Arc, + raw: bool, + sock: &rk_net::SockInode, + attr: &SetAttr, + change: Change, +) -> SysResult { + file.want_write()?; + let meta = file.metadata()?; + let _file = (!raw).then_some(file); + let cred = cx.process().cred(); + if change == Change::Touch && !cred.owns(&meta) { + cred.permission(&meta, rk_vfs::cred::may::WRITE)?; + } + let mut attr = *attr; + cred.setattr_prepare(&meta, &mut attr)?; + if change == Change::Owner { + cred.chown_kill(&meta, &mut attr); + } + sock.set_attr(&attr)?; + Ok(0) +} + pub(crate) fn fchmodat(cx: &Cx<'_>, dirfd: u64, path: u64, mode: u64) -> SysResult { let attr = SetAttr { mode: Some(mode as u32 & 0o7777), ..SetAttr::default() }; set_attr_at(cx, dirfd, path, true, &attr, Change::Plain) diff --git a/core/rk-syscall/src/net.rs b/core/rk-syscall/src/net.rs index c3685ee..07657b1 100644 --- a/core/rk-syscall/src/net.rs +++ b/core/rk-syscall/src/net.rs @@ -249,16 +249,23 @@ fn family_socket( } } +/// The owner of the inode of a new socket: the file system user and group IDs of the caller, as +/// `sock_alloc` of Linux. +fn sock_owner(cx: &Cx<'_>) -> (u32, u32) { + let cred = cx.process().cred(); + (cred.fsuid, cred.fsgid) +} + /// Adds the file of `sock` to the descriptor table. fn install(cx: &Cx<'_>, sock: Arc, flags: u32) -> Result { - let file = rk_net::new_file(sock, flags & O_NONBLOCK); + let file = rk_net::new_file(sock, flags & O_NONBLOCK, sock_owner(cx)); cx.files()?.lock().insert(file, flags & O_CLOEXEC != 0) } pub(crate) fn socket_call(cx: &Cx<'_>, domain: u64, kind: u64, protocol: u64) -> SysResult { let (kind, flags) = split_type(kind)?; let sock = new_socket(cx, domain, kind, protocol)?; - let file = rk_net::new_file(sock.clone(), flags & O_NONBLOCK); + let file = rk_net::new_file(sock.clone(), flags & O_NONBLOCK, sock_owner(cx)); rk_vfs::security::socket_post_create(&lsm_sock(&file, &*sock), false)?; Ok(cx.files()?.lock().insert(file, flags & O_CLOEXEC != 0)? as u64) } @@ -274,8 +281,8 @@ pub(crate) fn socketpair(cx: &Cx<'_>, domain: u64, kind: u64, protocol: u64, sv: rk_net_unix::check_protocol(u32::try_from(protocol).map_err(|_| Errno::EPROTONOSUPPORT)?)?; rk_vfs::security::socket_create(AF_UNIX, kind, protocol as u32, false)?; let (a, b) = Unix::pair(kind)?; - let fa = rk_net::new_file(a.clone(), flags & O_NONBLOCK); - let fb = rk_net::new_file(b.clone(), flags & O_NONBLOCK); + let fa = rk_net::new_file(a.clone(), flags & O_NONBLOCK, sock_owner(cx)); + let fb = rk_net::new_file(b.clone(), flags & O_NONBLOCK, sock_owner(cx)); let (sa, sb) = (lsm_sock(&fa, &*a), lsm_sock(&fb, &*b)); rk_vfs::security::socket_post_create(&sa, false)?; rk_vfs::security::socket_post_create(&sb, false)?; diff --git a/net/rk-net/src/lib.rs b/net/rk-net/src/lib.rs index d8889d6..823957f 100644 --- a/net/rk-net/src/lib.rs +++ b/net/rk-net/src/lib.rs @@ -38,7 +38,7 @@ pub mod veth; pub use options::{Options, Timeout}; -use alloc::sync::Arc; +use alloc::sync::{Arc, Weak}; use alloc::vec::Vec; use core::any::Any; use core::sync::atomic::{AtomicU64, Ordering}; @@ -310,11 +310,21 @@ fn sock_dev() -> u64 { } } +/// The owner, the group and the permission bits of the inode of a socket, as the inode of sockfs +/// of Linux. +#[derive(Clone, Copy)] +struct SockAttr { + uid: u32, + gid: u32, + mode: u32, +} + /// The operations of the file of a socket. struct SocketFile { socket: Arc, /// The socket blob of the security modules, as `sk_security` of Linux. security: rk_vfs::security::Blob, + attr: SpinLock, } impl Drop for SocketFile { @@ -377,11 +387,14 @@ impl FileOps for SocketFile { } fn metadata(&self) -> Option { + let attr = *self.attr.lock(); Some(Metadata { dev: sock_dev(), ino: self.socket.ino(), - mode: S_IFSOCK | 0o777, + mode: S_IFSOCK | attr.mode, nlink: 1, + uid: attr.uid, + gid: attr.gid, blksize: 4096, ..Metadata::default() }) @@ -392,10 +405,53 @@ impl FileOps for SocketFile { } } -/// Makes the file of `socket`. `flags` can hold `O_NONBLOCK`. -pub fn new_file(socket: Arc, flags: u32) -> Arc { +/// Makes the file of `socket`. `flags` can hold `O_NONBLOCK`. The inode gets the mode 0777 and +/// the owner `owner`, a user ID and a group ID, as `sock_alloc` of Linux gives it the file system +/// IDs of the caller. +pub fn new_file(socket: Arc, flags: u32, owner: (u32, u32)) -> Arc { let security = rk_vfs::security::Blob::sock(); - File::with_ops(Arc::new(SocketFile { socket, security }), None, O_RDWR | flags) + let attr = SpinLock::new(SockAttr { uid: owner.0, gid: owner.1, mode: 0o777 }); + File::with_ops(Arc::new(SocketFile { socket, security, attr }), None, O_RDWR | flags) +} + +/// The inode of a socket file, for a change of its attributes. It does not keep the socket open, +/// as a path of Linux keeps the inode of sockfs but not the file. The socket closes with its last +/// file, as `sock_close` of Linux. +pub struct SockInode(Weak); + +impl SockInode { + /// Gives the inode of `file`, or `None` if the file is not a socket. + pub fn of(file: &File) -> Option { + file.ops().as_any()?.downcast_ref::()?; + Some(Self(Arc::downgrade(file.ops()))) + } + + /// Changes the mode, the owner and the group, as `sockfs_setattr` of Linux. The other + /// attributes of `attr` have no effect. + /// + /// # Errors + /// + /// Gives `ENOENT` for a change of the owner after the socket closed, as the release of a + /// socket sets `sock->sk` to NULL in Linux (CVE-2019-8912). + pub fn set_attr(&self, attr: &rk_vfs::inode::SetAttr) -> Result<()> { + let Some(ops) = self.0.upgrade() else { + return if attr.uid.is_some() { Err(Errno::ENOENT) } else { Ok(()) }; + }; + let Some(file) = ops.as_any().and_then(|a| a.downcast_ref::()) else { + return Ok(()); + }; + let mut a = file.attr.lock(); + if let Some(mode) = attr.mode { + a.mode = mode & 0o7777; + } + if let Some(uid) = attr.uid { + a.uid = uid; + } + if let Some(gid) = attr.gid { + a.gid = gid; + } + Ok(()) + } } /// Gives the socket blob of the security modules of `file`, or `None` if the file is not a diff --git a/net/rk-net/src/tests.rs b/net/rk-net/src/tests.rs index 69452df..8ed5956 100644 --- a/net/rk-net/src/tests.rs +++ b/net/rk-net/src/tests.rs @@ -62,3 +62,23 @@ fn busy_poll() { assert_eq!(o.set(SO_BUSY_POLL, &(-1_i32).to_ne_bytes()), Err(Errno::EINVAL)); assert_eq!(o.get(SO_BUSY_POLL), Ok(50_i32.to_ne_bytes().to_vec())); } + +/// The inode of a socket file has the owner of `new_file` and the mode 0777, a change of the +/// attributes shows in `metadata`, and a change of the owner after the last file goes gives +/// `ENOENT`, as `sockfs_setattr` of Linux. +#[test] +fn socket_inode_attributes() { + use rk_vfs::inode::SetAttr; + let sock = crate::alg::AlgSocket::new(crate::consts::SOCK_SEQPACKET, 0).unwrap(); + let file = super::new_file(sock, 0, (1000, 100)); + let meta = file.metadata().unwrap(); + assert_eq!((meta.uid, meta.gid, meta.mode & 0o7777), (1000, 100, 0o777)); + let inode = super::SockInode::of(&file).unwrap(); + let attr = SetAttr { uid: Some(0), mode: Some(0o600), ..SetAttr::default() }; + assert_eq!(inode.set_attr(&attr), Ok(())); + let meta = file.metadata().unwrap(); + assert_eq!((meta.uid, meta.gid, meta.mode & 0o7777), (0, 100, 0o600)); + drop(file); + assert_eq!(inode.set_attr(&attr), Err(Errno::ENOENT)); + assert_eq!(inode.set_attr(&SetAttr { mode: Some(0o644), ..SetAttr::default() }), Ok(())); +}