Skip to content
Merged
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
32 changes: 32 additions & 0 deletions core/rk-syscall/src/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<File>,
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)
Expand Down
15 changes: 11 additions & 4 deletions core/rk-syscall/src/net.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<dyn Socket>, flags: u32) -> Result<i32, Errno> {
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)
}
Expand All @@ -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)?;
Expand Down
66 changes: 61 additions & 5 deletions net/rk-net/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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<dyn Socket>,
/// The socket blob of the security modules, as `sk_security` of Linux.
security: rk_vfs::security::Blob,
attr: SpinLock<SockAttr>,
}

impl Drop for SocketFile {
Expand Down Expand Up @@ -377,11 +387,14 @@ impl FileOps for SocketFile {
}

fn metadata(&self) -> Option<Metadata> {
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()
})
Expand All @@ -392,10 +405,53 @@ impl FileOps for SocketFile {
}
}

/// Makes the file of `socket`. `flags` can hold `O_NONBLOCK`.
pub fn new_file(socket: Arc<dyn Socket>, flags: u32) -> Arc<File> {
/// 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<dyn Socket>, flags: u32, owner: (u32, u32)) -> Arc<File> {
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<dyn FileOps>);

impl SockInode {
/// Gives the inode of `file`, or `None` if the file is not a socket.
pub fn of(file: &File) -> Option<Self> {
file.ops().as_any()?.downcast_ref::<SocketFile>()?;
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::<SocketFile>()) 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
Expand Down
20 changes: 20 additions & 0 deletions net/rk-net/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(()));
}
Loading