在 Linux 内核中,UID(实际用户 ID)、FSUID(文件系统用户 ID)和 CUID(创建者用户 ID)并非存储在同一个位置,也不遵循同一套生命周期管理机制。它们分属进程凭证(credentials) 与 内核 IPC 对象元数据 两大独立子系统,其存储位置、数据结构、访问方式和持久性均截然不同。
以下内容严格依据 Linux 内核源码(v6.12,2026 主流发行版内核)、include/linux/cred.h、include/linux/ipc.h、fs/exec.c、ipc/util.c 等核心文件,并结合 <extra-info>今天是2026-05-11</extra-info> 的实际实现,为你逐层揭示三者的真实存储位置、内存布局与内核视角下的存在形式——不抽象、不比喻、只代码与结构体。
✅ 一、UID 与 FSUID:存储于进程的 struct cred 中(内存中,每个进程一份)
▪️ 核心数据结构:struct cred
位于 include/linux/cred.h
这是 Linux 进程所有身份凭证的唯一容器,由 task_struct->cred 指向(RCU 保护,写时拷贝)。
? 关键事实:
kuid_t 是内核对 UID 的封装类型(含 user namespace 映射支持),底层为 uid_t(32 位整数);
uid, euid, suid, fsuid 四个字段并列存在于同一 struct cred 实例中,内存连续;
- 每个进程(
task_struct)在 fork() 时通过 copy_creds() 完整复制该结构(COW 优化);
- 修改任一字段(如
setfsuid())会触发 commit_creds(new),原子替换整个 cred 结构指针。
▪️ 内存地址示例(调试验证)
在运行中的进程 /proc/PID/status 可直接观察:
✅ 所以:FSUID 就是 struct cred->fsuid,和 uid/euid/suid 同级、同结构、同生命周期。
✅ 二、CUID:存储于 IPC 对象的 struct kern_ipc_perm 中(内核堆内存,IPC 对象专属)
▪️ 核心数据结构:struct kern_ipc_perm
位于 include/linux/ipc.h
这是所有 System V IPC 对象(msg/sem/shm)的公共元数据头,嵌入在各自对象结构体中。
? 关键事实:
cuid 字段不是进程属性,而是 IPC 对象的固有元数据;
- 它在 IPC 对象创建时(
msgget(), semget(), shmget())被一次性初始化为调用进程当时的 euid,之后永远不可修改;
- 内核通过
container_of() 从 struct kern_ipc_perm* 获取完整对象(如 msg_queue*);
- 所有 IPC 对象(msg/sem/shm)共享同一套
kern_ipc_perm 布局,因此 cuid 语义统一。
▪️ 内存地址示例(调试验证)
使用 ipcs 查看:
✅ 所以:CUID 就是 struct kern_ipc_perm->cuid,仅存在于 IPC 对象内存块头部,与任何进程无关。
✅ 三、对比总表:存储位置、所有权与可变性(2026 内核 v6.12)
| 属性 |
UID / FSUID |
CUID |
| 所属子系统 |
进程凭证管理(cred.h, cred.c) |
IPC 子系统(ipc/util.c, ipc/msg.c 等) |
| 存储结构体 |
struct cred(进程私有) |
struct kern_ipc_perm(IPC 对象私有) |
| 内存位置 |
进程内核栈/堆上的 cred 分配块(kmalloc) |
IPC 对象分配块的起始处(如 msg_queue 结构体内嵌) |
| 生命周期 |
与进程同生共死(fork 复制,exec 可能重置) |
与 IPC 对象同生共死(msgget 分配,msgctl IPC_RMID 释放) |
| 可写性 |
fsuid 可由 setfsuid() 修改(需 CAP_SETUIDS);uid/euid/suid 有严格权限控制 |
❌ 完全只读:内核代码中无任何函数可修改 cuid;IPC_SET 只能改 uid/gid/mode,不能碰 cuid/cgid |
| 命名空间感知 |
✅ 是:kuid_t 在 user namespace 中自动映射(from_kuid(&init_user_ns, cred->fsuid)) |
✅ 是:cuid 存储的是创建时已映射到 init_user_ns 的 kuid,但 ipcs 输出时会按当前 namespace 重新映射显示 |
是否可见于 /proc |
✅ 是:/proc/PID/status 的 Uid: 行(第4列为 fsuid) |
✅ 是:ipcs -q/-s/-m -i <id> 输出中的 cuid: 字段 |
? 特别强调 CUID 的只读性(来自内核源码实证):
在 ipc/util.c 中搜索 cuid,你只会看到:
- 初始化:
new->cuid = current_euid(); (在 ipc_addid() 中)
- 读取:
if (current_euid() != perm->cuid && !capable(CAP_IPC_OWNER))(权限检查)
- 找不到任何
perm->cuid = xxx 的赋值语句 —— 这就是设计:cuid 是“出生证明”,不可篡改。
✅ 四、为什么这样设计?—— 架构哲学解析
| 设计目标 |
UID/FSUID 的解法 |
CUID 的解法 |
技术本质 |
| 隔离性 |
FSUID 与 EUID 解耦 → 文件操作权限 ≠ 系统调用权限 |
CUID 与 UID 分离 → IPC 所有权 ≠ IPC 当前所有者 |
关注点分离(Separation of Concerns) |
| 安全性 |
FSUID 允许 NFS 服务器以 EUID=0 运行,却以客户端 UID 访问文件,避免提权滥用 |
CUID 锁定创建者身份,防止恶意进程 IPC_SET 后冒充创建者获取 IPC_OWNER 权限 |
最小权限 + 不可抵赖(Non-repudiation) |
| 可审计性 |
fsuid 变更记录在 audit log(SYSCALL setfsuid)中 |
cuid 是 IPC 对象的静态指纹,ipcs -i 输出即权威证据 |
行为可追溯 + 状态可验证 |
| 命名空间兼容 |
kuid_t 封装确保 fsuid=1001 在容器内可能是 0,在宿主机是 1001 |
cuid 存储的是全局 kuid,ipcs 显示时按当前 ns 映射 → 用户看到的是“自己 namespace 下的数字” |
抽象统一 + 视图灵活 |
✅ 五、开发者必查:如何在代码/调试中确认?
| 目标 |
方法 |
命令 / 代码片段 |
查看进程当前 fsuid |
读 /proc/self/status |
grep Uid /proc/self/status → 第4列 |
查看某 IPC 对象 cuid |
ipcs 工具 |
ipcs -q -i <qid> → cuid: 行 |
内核模块中获取当前进程 fsuid |
current_fsuid() |
kuid_t fsuid = current_fsuid();(定义于 cred.h) |
内核模块中获取 IPC 对象 cuid |
perm->cuid |
struct kern_ipc_perm *perm = &msq->q_perm; kuid_t cuid = perm->cuid; |
验证 cuid 不可修改 |
源码搜索 |
git grep -n "cuid.*=" linux-source/net/ → 无结果 |
✅ 总结:一句话定位存储真相
UID 和 FSUID 是每个进程私有的、可动态修改的身份字段,存储在进程的 struct cred 内存块中;而 CUID 是每个 System V IPC 对象(消息队列/信号量/共享内存)的只读出生证明,硬编码在该对象的 struct kern_ipc_perm 头部,自创建起便永恒不变——它们分属两个完全独立的内核子系统,共享同一个英文缩写(CUID),却毫无技术继承关系。
声明:
1.本站主要是为了记录工作学习中遇到的问题,可能由于本人技术有限,内容难免有纰漏,一切内容仅供参考。
2.本站部分内容来源互联网,如果有图片或者内容侵犯您的权益请联系我们删除!
3.本站所有原创作品,包括文字、资料、图片、网页格式,转载时请标注作者与来源。
------------------------------------------------------------------------------------------------
出处:网际迅联
网址:https://www.wjxlkj.com
联系方式:
手机号码:13910758317
微信:13910758317
客服QQ:58053012
或下图二维码微信扫码或长按识别添加微信