socket_peercred / SCM_CREDENTIALS: UB when kernel returns pid 0
维护者通常 4 天内回复
还没有人认领这个 Issue。
评估
- 难度
- 4/5
- 预计耗时
- 3-5 天
- 新手友好度
- 42/100
- Issue 类型
- 缺陷
- 描述清晰度
- 基本清楚
- 活跃度
- 活跃
- 技术栈
- linux, rust
调研方向
Start with UCred and Pid in src/net/types.rs, then trace socket_peercred in src/backend/linux_raw/net/sockopt.rs and src/backend/libc/net/sockopt.rs, plus RecvAncillaryMessage::ScmCredentials where the issue identifies an unaligned read. The issue proposes representing an absent pid explicitly and converting from a raw integer-field struct; inspect related issues #1696 and #1678 before choosing the change. Done means both credential paths handle kernel pid 0 without constructing an invalid Pid.
由索引模型根据 Issue 内容生成。
描述
The Linux kernel returns a pid of 0 in a struct ucred in several situations. Rustix turns that struct directly into UCred without checking it. The result is a Pid (NonZeroI32) holding 0, which is undefined behavior.
Where
UCredis#[repr(C)] { pid: Pid, uid: Uid, gid: Gid }andPidwrapsNonZeroI32(src/net/types.rs)socket_peercredisgetsockopt::<UCred>(fd, SOL_SOCKET, SO_PEERCRED)(src/backend/linux_raw/net/sockopt.rs,src/backend/libc/net/sockopt.rs):getsockoptfills aMaybeUninit<T>and callsassume_init()but withUCreda pid of 0 is an invalid bit patternRecvAncillaryMessage::ScmCredentialsusespayload.as_ptr().cast::<UCred>().read_unaligned()
When is it 0
socket_peercred / SCM_CREDENTIALS use pid_vnr(), which translates a struct pid into the reader's pid namespace. It returns 0 when the pid is NULL or not visible in that namespace. (reference: https://github.com/torvalds/linux/blob/67f0943b394d920b6c142aad8c6af94340342ae7/kernel/pid.c#L542-L560)
SO_PEERCRED->cred_to_ucred(sk->sk_peer_pid, …)->ucred->pid = pid_vnr(pid)(reference: https://github.com/torvalds/linux/blob/67f0943b394d920b6c142aad8c6af94340342ae7/net/core/sock.c#L1704-L1715)SCM_CREDENTIALSon receive ->unix_skb_to_scm->scm_set_cred->scm->creds.pid = pid_vnr(pid)(reference: https://github.com/torvalds/linux/blob/67f0943b394d920b6c142aad8c6af94340342ae7/include/net/scm.h#L69-L76)
So it's 0 for:
- socket with no peer credentials e.g. TCP socket or unconnected UNIX socket (
SO_PEERCREDsucceeds with pid 0 and uid/gid −1) - peer that's not visible in the reader's pid namespace e.g. process in a container reading credentials of a peer on the host or in a sibling container. https://man7.org/linux/man-pages/man7/pid_namespaces.7.html mentions that pids passed over Unix sockets are translated into the receiving process's pid namespace.
SCM_CREDENTIALSsent without credentials e.g. a message sent before the receiver enabled 'SO_PASSCRED' on a stream socket
https://man7.org/linux/man-pages/man7/unix.7.html and https://man7.org/linux/man-pages/man7/socket.7.html describe SO_PEERCRED and SCM_CREDENTIALS but don't mention the 0 case but see the kernel code references above.
Related
#1696 (getpgid) and #1678 (tcgetpgrp on macOS) are also cases of a pid of 0 from the kernel turned into a Pid.
Reproduction
Cargo.toml:
[package]
name = "repro"
version = "0.1.0"
edition = "2024"
[dependencies]
libc = "0.2"
# "process" only because "net" alone does not build (#1689).
rustix = { version = "=1.1.5", features = ["net", "process"] }
[features]
use-libc = ["rustix/use-libc"]
use std::io::{IoSliceMut, Write};
use std::mem::MaybeUninit;
use std::os::fd::AsRawFd;
use std::os::unix::net::{UnixDatagram, UnixStream};
use rustix::net::{RecvAncillaryBuffer, RecvAncillaryMessage, RecvFlags, recvmsg, sockopt};
fn main() {
so_peercred_pid_0();
scm_credentials_pid_0();
}
/// `socket_peercred`: An unbound socket has no peer, so SO_PEERCRED succeeds with pid 0 but rustix returns an error.
fn so_peercred_pid_0() {
// An unbound socket has no peer, so SO_PEERCRED succeeds with pid 0.
let socket = UnixDatagram::unbound().unwrap();
let mut cred = libc::ucred { pid: -1, uid: 0, gid: 0 };
let mut len = size_of::<libc::ucred>() as libc::socklen_t;
let ret = unsafe {
libc::getsockopt(
socket.as_raw_fd(),
libc::SOL_SOCKET,
libc::SO_PEERCRED,
(&raw mut cred).cast(),
&mut len,
)
};
println!("SO_PEERCRED libc: ret={ret} pid={} uid={} gid={}", cred.pid, cred.uid as i32, cred.gid as i32);
println!("SO_PEERCRED rustix: {:?}", sockopt::socket_peercred(&socket));
}
/// `RecvAncillaryMessage::ScmCredentials`: message sent before the receiver set SO_PASSCRED has no credentials so kernel succeeds with pid 0
fn scm_credentials_pid_0() {
let (mut sender, receiver) = UnixStream::pair().unwrap();
sender.write_all(b"x").unwrap();
sockopt::set_socket_passcred(&receiver, true).unwrap();
let mut space = [MaybeUninit::uninit(); rustix::cmsg_space!(ScmCredentials(1))];
let mut control = RecvAncillaryBuffer::new(&mut space);
recvmsg(&receiver, &mut [IoSliceMut::new(&mut [0])], &mut control, RecvFlags::empty()).unwrap();
for message in control.drain() {
if let RecvAncillaryMessage::ScmCredentials(cred) = message {
// A `Pid` can't be 0 so the compiler may turn this into `false`.
println!(
"SCM_CREDENTIALS rustix: {cred:?}, pid == 0: {}",
cred.pid.as_raw_pid() == 0
);
}
}
}
uid is returned as Errno
raw: cargo run - uid bits truncated to 16 bits and negated: -1 -> 1 (EPERM)
SO_PEERCRED libc: ret=0 pid=0 uid=-1 gid=-1
SO_PEERCRED rustix: Err(Os { code: 1, kind: PermissionDenied, message: "Operation not permitted" })
SCM_CREDENTIALS rustix: UCred { pid: Pid(0), uid: Uid(65534), gid: Gid(65534) }, pid == 0: true
libc: cargo run --features use-libc - uid -1 -> -1
SO_PEERCRED libc: ret=0 pid=0 uid=-1 gid=-1
SO_PEERCRED rustix: Err(Os { code: -1, kind: Uncategorized, message: "Unknown error -1" })
SCM_CREDENTIALS rustix: UCred { pid: Pid(0), uid: Uid(65534), gid: Gid(65534) }, pid == 0: true
Idea
(Breaking) make UCred::pid an Option<Pid>: make explicit that a pid may be absent. std's UCred does the same.
- read the kernel's bytes into a private raw struct e.g.
RawUCredwith plain integer fields and convert explicitly msg.rscan do the same withread_unaligned::<RawUCred>()
- 主要语言
- Rust
- 星标
- 2.1k
- 派生
- 301
- 平均合并
- 10 天 1 小时
- 30 天内合并 PR
- 1
环境准备
- 没有 Dockerfile 或 Docker Compose 文件
- 没有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
bytecodealliance/rustix 的其他 Issue
-
net feature alone fails to compile in 1.1.5: sockopt uses crate::timespec, which net does not gate in可能重新可做 关联的 PR 已关闭且未合并。 未关闭
难度 2/5 1-3 小时 新手友好度 70/100
bytecodealliance/rustix#1689 · 2 条评论 ·
维护者通常 4 天内回复
-
Wrong flag used for (set_)ipv6_multicast_hops可能已有人在做 @RajaBabu15 于 54 天前认领。 未关闭
难度 2/5 1-3 小时 新手友好度 84/100
bytecodealliance/rustix#1660 ·
维护者通常 4 天内回复
-
难度 2/5 1-3 小时 新手友好度 68/100
bytecodealliance/rustix#1635 ·
维护者通常 4 天内回复
-
enhancement
难度 2/5 1-3 小时 新手友好度 62/100
bytecodealliance/rustix#1068 ·
维护者通常 4 天内回复
-
难度 5/5 一周以上 新手友好度 30/100
bytecodealliance/rustix#1696 ·
维护者通常 4 天内回复
查看 bytecodealliance/rustix 的全部 Issue
相似的 Issue
-
难度 2/5 1-3 小时 新手友好度 75/100
维护者通常 4 天内回复
-
`future_into_py` loses the original panic message可能已有人在做 @Danipulok 今天认领。 未关闭
难度 1/5 1 小时以内 新手友好度 90/100
PyO3/pyo3-async-runtimes#91 ·
-
难度 2/5 1-3 小时 新手友好度 75/100
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 75/100
维护者通常 1 天内回复
-
难度 2/5 1 小时以内 新手友好度 85/100
维护者通常 2 天内回复