Support SO_PEERCRED & SO_PEERGROUPS

This commit is contained in:
jiangjianfeng 2025-06-11 08:28:05 +00:00 committed by Ruihan Li
parent d7e88f93bd
commit e4c5c36be9
16 changed files with 282 additions and 23 deletions

View File

@ -52,6 +52,7 @@ typeflags::typeflags! {
pub type Full = TRightSet<TRights![Dup, Read, Write, Exec, Signal]>;
pub type ReadOp = TRights![Read];
pub type WriteOp = TRights![Write];
pub type ReadDupOp = TRights![Read, Dup];
pub type FullOp = TRights![Read, Write, Dup];
/// Wrapper for TRights, used to bypass an error message from the Rust compiler,

View File

@ -1,7 +1,7 @@
// SPDX-License-Identifier: MPL-2.0
use super::util::LingerOption;
use crate::{impl_socket_options, prelude::*};
use crate::{impl_socket_options, net::socket::unix::CUserCred, prelude::*, process::Gid};
mod macros;
@ -21,7 +21,9 @@ impl_socket_options!(
pub struct Priority(i32);
pub struct Linger(LingerOption);
pub struct KeepAlive(bool);
pub struct PeerCred(CUserCred);
pub struct AcceptConn(bool);
pub struct SendBufForce(u32);
pub struct RecvBufForce(u32);
pub struct PeerGroups(Arc<[Gid]>);
);

View File

@ -0,0 +1,87 @@
// SPDX-License-Identifier: MPL-2.0
use aster_rights::{Dup, Read, ReadDupOp, ReadOp, TRights};
use aster_rights_proc::require;
use crate::{
prelude::*,
process::{posix_thread::AsPosixThread, Credentials, Gid, Pid, Uid},
};
pub(super) struct SocketCred<R = ReadOp> {
pid: Pid,
cred: Credentials<R>,
}
impl SocketCred<ReadOp> {
pub(super) fn new_current() -> Self {
let pid = current!().pid();
let cred = current_thread!().as_posix_thread().unwrap().credentials();
Self { pid, cred }
}
}
impl SocketCred<ReadDupOp> {
pub(super) fn new_current() -> Self {
let pid = current!().pid();
let cred = current_thread!()
.as_posix_thread()
.unwrap()
.credentials_dup();
Self { pid, cred }
}
}
impl<R: TRights> SocketCred<R> {
#[require(R > Read)]
pub(super) fn to_c_user_cred(&self) -> CUserCred {
CUserCred {
pid: self.pid,
uid: self.cred.euid(),
gid: self.cred.egid(),
}
}
#[require(R > Read)]
pub(super) fn groups(&self) -> Arc<[Gid]> {
self.cred.groups().iter().cloned().collect()
}
#[require(R > R1)]
pub(super) fn restrict<R1: TRights>(self) -> SocketCred<R1> {
let Self { pid, cred } = self;
SocketCred {
pid,
cred: cred.restrict(),
}
}
#[require(R > Dup)]
pub(super) fn dup(&self) -> Self {
Self {
pid: self.pid,
cred: self.cred.dup(),
}
}
}
/// Reference: <https://elixir.bootlin.com/linux/v6.15/source/include/linux/socket.h#L183>.
#[derive(Debug, Clone, Copy, Pod)]
#[repr(C)]
pub struct CUserCred {
pid: Pid,
uid: Uid,
gid: Gid,
}
impl CUserCred {
pub(in crate::net) const fn new_unknown() -> Self {
Self {
pid: 0,
uid: Uid::INVALID,
gid: Gid::INVALID,
}
}
}

View File

@ -1,9 +1,11 @@
// SPDX-License-Identifier: MPL-2.0
mod addr;
mod cred;
mod ns;
mod stream;
pub use addr::UnixSocketAddr;
pub use cred::CUserCred;
pub use stream::UnixStreamSocket;
pub(super) use stream::UNIX_STREAM_DEFAULT_BUF_SIZE;

View File

@ -4,7 +4,7 @@ use crate::{
events::IoEvents,
fs::utils::{Endpoint, EndpointState},
net::socket::{
unix::{addr::UnixSocketAddrBound, UnixSocketAddr},
unix::{addr::UnixSocketAddrBound, cred::SocketCred, UnixSocketAddr},
util::SockShutdownCmd,
},
prelude::*,
@ -19,6 +19,7 @@ pub(super) struct Connected {
inner: Endpoint<Inner>,
reader: Mutex<RbConsumer<u8>>,
writer: Mutex<RbProducer<u8>>,
peer_cred: SocketCred,
}
impl Connected {
@ -27,6 +28,8 @@ impl Connected {
peer_addr: Option<UnixSocketAddrBound>,
state: EndpointState,
peer_state: EndpointState,
cred: SocketCred,
peer_cred: SocketCred,
) -> (Connected, Connected) {
let (this_writer, peer_reader) = RingBuffer::new(UNIX_STREAM_DEFAULT_BUF_SIZE).split();
let (peer_writer, this_reader) = RingBuffer::new(UNIX_STREAM_DEFAULT_BUF_SIZE).split();
@ -46,11 +49,13 @@ impl Connected {
inner: this_inner,
reader: Mutex::new(this_reader),
writer: Mutex::new(this_writer),
peer_cred,
};
let peer = Connected {
inner: peer_inner,
reader: Mutex::new(peer_reader),
writer: Mutex::new(peer_writer),
peer_cred: cred,
};
(this, peer)
@ -144,6 +149,10 @@ impl Connected {
pub(super) fn cloned_pollee(&self) -> Pollee {
self.inner.this_end().state.cloned_pollee()
}
pub(super) fn peer_cred(&self) -> &SocketCred {
&self.peer_cred
}
}
impl Drop for Connected {

View File

@ -2,6 +2,8 @@
use core::sync::atomic::{AtomicBool, Ordering};
use aster_rights::ReadOp;
use super::{
connected::Connected,
listener::Listener,
@ -11,7 +13,10 @@ use crate::{
events::IoEvents,
fs::utils::EndpointState,
net::socket::{
unix::addr::{UnixSocketAddr, UnixSocketAddrBound},
unix::{
addr::{UnixSocketAddr, UnixSocketAddrBound},
cred::SocketCred,
},
util::SockShutdownCmd,
},
prelude::*,
@ -48,6 +53,7 @@ impl Init {
self,
peer_addr: UnixSocketAddrBound,
pollee: Pollee,
peer_cred: SocketCred,
) -> (Connected, Connected) {
let Init {
addr,
@ -56,11 +62,15 @@ impl Init {
} = self;
pollee.invalidate();
let cred = SocketCred::<ReadOp>::new_current();
let (this_conn, peer_conn) = Connected::new_pair(
addr,
Some(peer_addr),
EndpointState::new(pollee, is_read_shutdown.into_inner()),
EndpointState::new(Pollee::new(), is_write_shutdown.into_inner()),
cred,
peer_cred,
);
(this_conn, peer_conn)

View File

@ -2,6 +2,7 @@
use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use aster_rights::ReadDupOp;
use ostd::sync::WaitQueue;
use super::{
@ -16,6 +17,7 @@ use crate::{
net::socket::{
unix::{
addr::{UnixSocketAddrBound, UnixSocketAddrKey},
cred::SocketCred,
stream::socket::OptionSet,
},
util::{SockShutdownCmd, SocketAddr},
@ -87,6 +89,10 @@ impl Listener {
pub(super) fn check_io_events(&self) -> IoEvents {
self.backlog.check_io_events()
}
pub(super) fn cred(&self) -> &SocketCred<ReadDupOp> {
&self.backlog.listener_cred
}
}
impl Drop for Listener {
@ -146,6 +152,7 @@ pub(super) struct Backlog {
backlog: AtomicUsize,
incoming_conns: SpinLock<Option<VecDeque<Connected>>>,
wait_queue: WaitQueue,
listener_cred: SocketCred<ReadDupOp>,
}
impl Backlog {
@ -162,6 +169,7 @@ impl Backlog {
backlog: AtomicUsize::new(backlog),
incoming_conns: SpinLock::new(incoming_sockets),
wait_queue: WaitQueue::new(),
listener_cred: SocketCred::<ReadDupOp>::new_current(),
}
}
@ -248,7 +256,11 @@ impl Backlog {
));
}
let (client_conn, server_conn) = init.into_connected(self.addr.clone(), pollee);
let (client_conn, server_conn) = init.into_connected(
self.addr.clone(),
pollee,
self.listener_cred.dup().restrict(),
);
incoming_conns.push_back(server_conn);
self.pollee.notify(IoEvents::IN);

View File

@ -2,6 +2,7 @@
use core::sync::atomic::{AtomicBool, Ordering};
use aster_rights::ReadDupOp;
use takeable::Takeable;
use super::{
@ -12,10 +13,11 @@ use super::{
use crate::{
events::IoEvents,
fs::{file_handle::FileLike, utils::EndpointState},
match_sock_option_mut,
net::socket::{
options::SocketOption,
options::{PeerCred, PeerGroups, SocketOption},
private::SocketPrivate,
unix::UnixSocketAddr,
unix::{cred::SocketCred, CUserCred, UnixSocketAddr},
util::{
options::{GetSocketLevelOption, SetSocketLevelOption, SocketOptionSet},
MessageHeader, SendRecvFlags, SockShutdownCmd, SocketAddr,
@ -23,7 +25,10 @@ use crate::{
Socket,
},
prelude::*,
process::signal::{PollHandle, Pollable, Pollee},
process::{
signal::{PollHandle, Pollable, Pollee},
Gid,
},
util::{MultiRead, MultiWrite},
};
@ -115,6 +120,24 @@ impl State {
State::Connected(connected) => connected.is_write_shutdown(),
}
}
pub(self) fn peer_cred(&self) -> Option<CUserCred> {
match self {
Self::Init(_) => None,
Self::Listen(listener) => Some(listener.cred().to_c_user_cred()),
Self::Connected(connected) => Some(connected.peer_cred().to_c_user_cred()),
}
}
pub(self) fn peer_groups(&self) -> Result<Arc<[Gid]>> {
match self {
State::Init(_) => {
return_errno_with_message!(Errno::ENODATA, "the socket does not have peer groups")
}
State::Listen(listener) => Ok(listener.cred().groups()),
State::Connected(connected) => Ok(connected.peer_cred().groups()),
}
}
}
#[derive(Clone, Debug)]
@ -136,11 +159,15 @@ impl UnixStreamSocket {
}
pub fn new_pair(is_nonblocking: bool) -> (Arc<Self>, Arc<Self>) {
let cred = SocketCred::<ReadDupOp>::new_current();
let (conn_a, conn_b) = Connected::new_pair(
None,
None,
EndpointState::default(),
EndpointState::default(),
cred.dup().restrict(),
cred.restrict(),
);
let options = OptionSet::new();
(
@ -378,6 +405,12 @@ impl Socket for UnixStreamSocket {
let state = self.state.read();
let options = self.options.read();
// Deal with UNIX-socket-specific socket-level options
match do_unix_getsockopt(option, state.as_ref()) {
Err(err) if err.error() == Errno::ENOPROTOOPT => (),
res => return res,
}
// Deal with socket-level options
match options.socket.get_option(option, state.as_ref()) {
Err(err) if err.error() == Errno::ENOPROTOOPT => (),
@ -409,6 +442,22 @@ impl Socket for UnixStreamSocket {
}
}
fn do_unix_getsockopt(option: &mut dyn SocketOption, state: &State) -> Result<()> {
match_sock_option_mut!(option, {
socket_peer_cred: PeerCred => {
let peer_cred = state.peer_cred().unwrap_or_else(CUserCred::new_unknown);
socket_peer_cred.set(peer_cred);
},
socket_peer_groups: PeerGroups => {
let groups = state.peer_groups()?;
socket_peer_groups.set(groups);
},
_ => return_errno_with_message!(Errno::ENOPROTOOPT, "the socket option to get is not unix socket specific")
});
Ok(())
}
impl GetSocketLevelOption for State {
fn is_listening(&self) -> bool {
matches!(self, Self::Listen(_))

View File

@ -11,10 +11,10 @@ use crate::{
match_sock_option_mut, match_sock_option_ref,
net::socket::{
options::{
AcceptConn, KeepAlive, Linger, Priority, RecvBuf, RecvBufForce, ReuseAddr, ReusePort,
SendBuf, SendBufForce, SocketOption,
AcceptConn, KeepAlive, Linger, PeerCred, PeerGroups, Priority, RecvBuf, RecvBufForce,
ReuseAddr, ReusePort, SendBuf, SendBufForce, SocketOption,
},
unix::UNIX_STREAM_DEFAULT_BUF_SIZE,
unix::{CUserCred, UNIX_STREAM_DEFAULT_BUF_SIZE},
},
prelude::*,
process::{credentials::capabilities::CapSet, posix_thread::AsPosixThread},
@ -114,6 +114,10 @@ impl SocketOptionSet {
let keep_alive = self.keep_alive();
socket_keepalive.set(keep_alive);
},
socket_peer_cred: PeerCred => {
let peer_cred = CUserCred::new_unknown();
socket_peer_cred.set(peer_cred);
},
socket_accept_conn: AcceptConn => {
let is_listening = socket.is_listening();
socket_accept_conn.set(is_listening);
@ -128,6 +132,9 @@ impl SocketOptionSet {
let recv_buf = self.recv_buf();
socket_recvbuf_force.set(recv_buf);
},
_socket_peer_groups: PeerGroups => {
return_errno_with_message!(Errno::ENODATA, "the socket does not have peer groups");
},
_ => return_errno_with_message!(Errno::ENOPROTOOPT, "the socket option to get is unknown")
});
Ok(())

View File

@ -11,6 +11,11 @@ use crate::prelude::*;
pub struct Gid(u32);
impl Gid {
/// The invalid GID, typically used to indicate that no valid GID is found when returning to user space.
///
/// Reference: <https://elixir.bootlin.com/linux/v6.15/source/include/linux/uidgid.h#L51>.
pub const INVALID: Gid = Gid(u32::MAX);
pub const fn new(gid: u32) -> Self {
Self(gid)
}

View File

@ -13,6 +13,11 @@ pub struct Uid(u32);
const ROOT_UID: u32 = 0;
impl Uid {
/// The invalid UID, typically used to indicate that no valid UID is found when returning to user space.
///
/// Reference: <https://elixir.bootlin.com/linux/v6.15/source/include/linux/uidgid.h#L50>.
pub const INVALID: Uid = Self::new(u32::MAX);
pub const fn new_root() -> Self {
Self(ROOT_UID)
}

View File

@ -2,7 +2,7 @@
use core::sync::atomic::{AtomicU32, Ordering};
use aster_rights::{ReadOp, WriteOp};
use aster_rights::{ReadDupOp, ReadOp, WriteOp};
use ostd::sync::{RoArc, Waker};
use super::{
@ -291,6 +291,11 @@ impl PosixThread {
self.credentials.dup().restrict()
}
/// Gets the duplicatable read-only credentials of the thread.
pub fn credentials_dup(&self) -> Credentials<ReadDupOp> {
self.credentials.dup().restrict()
}
/// Gets the write-only credentials of the current thread.
///
/// It is illegal to mutate the credentials from a thread other than the

View File

@ -16,8 +16,8 @@ pub fn sys_getsockopt(
ctx: &Context,
) -> Result<SyscallReturn> {
let level = CSocketOptionLevel::try_from(level).map_err(|_| Errno::EOPNOTSUPP)?;
if optval == 0 || optlen_addr == 0 {
return_errno_with_message!(Errno::EINVAL, "optval or optlen_addr is null pointer");
if optlen_addr == 0 {
return_errno_with_message!(Errno::EINVAL, "optlen_addr is null pointer");
}
let user_space = ctx.user_space();
@ -34,7 +34,14 @@ pub fn sys_getsockopt(
socket.get_option(raw_option.as_sock_option_mut())?;
let write_len = raw_option.write_to_user(optval, optlen)?;
let write_len = {
let mut new_opt_len = optlen;
let res = raw_option.write_to_user(optval, &mut new_opt_len);
if new_opt_len != optlen {
user_space.write_val(optlen_addr, &new_opt_len)?;
}
res?
};
user_space.write_val(optlen_addr, &(write_len as u32))?;
Ok(SyscallReturn::Return(0))

View File

@ -67,7 +67,7 @@ use self::{socket::new_socket_option, tcp::new_tcp_option};
pub trait RawSocketOption: SocketOption {
fn read_from_user(&mut self, addr: Vaddr, max_len: u32) -> Result<()>;
fn write_to_user(&self, addr: Vaddr, max_len: u32) -> Result<usize>;
fn write_to_user(&self, addr: Vaddr, max_len: &mut u32) -> Result<usize>;
fn as_sock_option_mut(&mut self) -> &mut dyn SocketOption;
@ -87,11 +87,11 @@ macro_rules! impl_raw_socket_option {
Ok(())
}
fn write_to_user(&self, addr: Vaddr, max_len: u32) -> Result<usize> {
fn write_to_user(&self, addr: Vaddr, max_len: &mut u32) -> Result<usize> {
use $crate::util::net::options::utils::WriteToUser;
let output = self.get().unwrap();
output.write_to_user(addr, max_len)
output.write_to_user(addr, *max_len)
}
fn as_sock_option_mut(&mut self) -> &mut dyn SocketOption {
@ -114,11 +114,11 @@ macro_rules! impl_raw_sock_option_get_only {
return_errno_with_message!(Errno::ENOPROTOOPT, "the option is getter-only");
}
fn write_to_user(&self, addr: Vaddr, max_len: u32) -> Result<usize> {
fn write_to_user(&self, addr: Vaddr, max_len: &mut u32) -> Result<usize> {
use $crate::util::net::options::utils::WriteToUser;
let output = self.get().unwrap();
output.write_to_user(addr, max_len)
output.write_to_user(addr, *max_len)
}
fn as_sock_option_mut(&mut self) -> &mut dyn SocketOption {
@ -145,7 +145,7 @@ macro_rules! impl_raw_sock_option_set_only {
Ok(())
}
fn write_to_user(&self, _addr: Vaddr, _max_len: u32) -> Result<usize> {
fn write_to_user(&self, _addr: Vaddr, _max_len: &mut u32) -> Result<usize> {
return_errno_with_message!(Errno::ENOPROTOOPT, "the option is setter-only");
}

View File

@ -2,12 +2,13 @@
use super::RawSocketOption;
use crate::{
impl_raw_sock_option_get_only, impl_raw_socket_option,
current_userspace, impl_raw_sock_option_get_only, impl_raw_socket_option,
net::socket::options::{
AcceptConn, Error, KeepAlive, Linger, Priority, RecvBuf, RecvBufForce, ReuseAddr,
ReusePort, SendBuf, SendBufForce, SocketOption,
AcceptConn, Error, KeepAlive, Linger, PeerCred, PeerGroups, Priority, RecvBuf,
RecvBufForce, ReuseAddr, ReusePort, SendBuf, SendBufForce, SocketOption,
},
prelude::*,
process::Gid,
};
/// Socket level options.
@ -33,9 +34,15 @@ enum CSocketOptionName {
LINGER = 13,
BSDCOMPAT = 14,
REUSEPORT = 15,
PASSCRED = 16,
PEERCRED = 17,
ATTACH_FILTER = 26,
DETACH_FILTER = 27,
ACCPETCONN = 30,
PEERSEC = 31,
SNDBUFFORCE = 32,
RCVBUFFORCE = 33,
PEERGROUPS = 59,
RCVTIMEO_NEW = 66,
SNDTIMEO_NEW = 67,
}
@ -51,9 +58,11 @@ pub fn new_socket_option(name: i32) -> Result<Box<dyn RawSocketOption>> {
CSocketOptionName::PRIORITY => Ok(Box::new(Priority::new())),
CSocketOptionName::LINGER => Ok(Box::new(Linger::new())),
CSocketOptionName::KEEPALIVE => Ok(Box::new(KeepAlive::new())),
CSocketOptionName::PEERCRED => Ok(Box::new(PeerCred::new())),
CSocketOptionName::ACCPETCONN => Ok(Box::new(AcceptConn::new())),
CSocketOptionName::SNDBUFFORCE => Ok(Box::new(SendBufForce::new())),
CSocketOptionName::RCVBUFFORCE => Ok(Box::new(RecvBufForce::new())),
CSocketOptionName::PEERGROUPS => Ok(Box::new(PeerGroups::new())),
_ => return_errno_with_message!(Errno::ENOPROTOOPT, "unsupported socket-level option"),
}
}
@ -66,6 +75,40 @@ impl_raw_socket_option!(ReusePort);
impl_raw_socket_option!(Priority);
impl_raw_socket_option!(Linger);
impl_raw_socket_option!(KeepAlive);
impl_raw_sock_option_get_only!(PeerCred);
impl_raw_sock_option_get_only!(AcceptConn);
impl_raw_socket_option!(SendBufForce);
impl_raw_socket_option!(RecvBufForce);
// SO_PEERGROUPS is a read-only option. However, calling setsockopt on SO_PEERGROUPS will return EINVAL
// instead of ENOPROTOOPT like other options. Therefore, we manually implement `RawSocketOption` for it.
impl RawSocketOption for PeerGroups {
fn read_from_user(&mut self, _addr: Vaddr, _max_len: u32) -> Result<()> {
return_errno_with_message!(Errno::EINVAL, "the option is getter-only");
}
fn write_to_user(&self, addr: Vaddr, buffer_len: &mut u32) -> Result<usize> {
let groups = self.get().unwrap();
let old_len = *buffer_len;
*buffer_len = (groups.len() * core::mem::size_of::<Gid>()) as u32;
if old_len < *buffer_len {
return_errno_with_message!(Errno::ERANGE, "the buffer is too small");
}
for (i, gid) in groups.iter().enumerate() {
let dst = addr + i * core::mem::size_of::<Gid>();
current_userspace!().write_val(dst, gid)?;
}
Ok(*buffer_len as usize)
}
fn as_sock_option_mut(&mut self) -> &mut dyn SocketOption {
self
}
fn as_sock_option(&self) -> &dyn SocketOption {
self
}
}

View File

@ -6,6 +6,7 @@ use crate::{
current_userspace,
net::socket::{
ip::{options::IpTtl, stream_options::CongestionControl},
unix::CUserCred,
util::LingerOption,
},
prelude::*,
@ -225,3 +226,17 @@ impl From<CLinger> for LingerOption {
LingerOption::new(is_on, timeout)
}
}
impl WriteToUser for CUserCred {
fn write_to_user(&self, addr: Vaddr, max_len: u32) -> Result<usize> {
let write_len = core::mem::size_of::<CUserCred>();
if (max_len as usize) < write_len {
return_errno_with_message!(Errno::EINVAL, "max_len is too short");
};
current_userspace!().write_val(addr, self)?;
Ok(write_len)
}
}