为何setuid切换用户后无法访问/proc/self/fd/N?
测试代码
多行Python代码
import os f=os.open("/etc/shadow", os.O_RDONLY) os.setuid(65535) os.open(f"/proc/self/fd/{f}", os.O_RDONLY)
单行执行命令
python3 -c 'import os; f=os.open("/etc/shadow", os.O_RDONLY); os.setuid(65535); os.open(f"/proc/self/fd/{f}", os.O_RDONLY)'
核心疑问
- 以root身份打开
/etc/shadow后切换到nobody用户(UID 65535),尝试通过/proc/self/fd/N重新打开该文件时得到-EACCES错误,但根据proc_fd_permission的注释预期应该成功,原因是什么? proc_fd_permission的源码注释实际想表达什么?
补充疑问
若权限限制仅针对符号链接本身而非目标文件,为何能通过/proc/self/fd/N访问套接字和已删除文件?例如执行以下命令能正常输出hello:
exec 3>foo; echo hello >&3; rm foo; cat /proc/self/fd/3
为什么重新打开/proc/self/fd/N会失败?
核心在于**open()系统调用处理/proc/self/fd/N的逻辑**:当调用open("/proc/self/fd/N", O_RDONLY)时,内核会先解析这个符号链接到原文件路径(比如/etc/shadow),然后对原文件路径执行权限检查——这个检查基于当前进程的有效UID/GID,而非你是否持有该文件描述符。
切换到UID 65535后,尽管进程还握着/etc/shadow的文件描述符,但重新打开时,内核会以nobody的身份去检查/etc/shadow的权限,显然nobody没有读取权限,因此返回-EACCES。
proc_fd_permission注释的实际含义
proc_fd_permission只负责**/proc/self/fd/N这个符号链接本身的权限校验**,比如允许你用readlink()获取它指向的路径,只要进程拥有对应的文件描述符,就能通过这个符号链接的权限检查。但它管不到后续open()目标文件的步骤——后者的权限检查是独立的,完全基于当前用户对目标文件的访问权限。注释想表达的是符号链接本身的访问规则,而非重新打开目标文件的规则。
关于套接字和已删除文件的例外情况
对于套接字、已删除文件这类特殊对象,内核无法通过/proc/self/fd/N解析出一个有效的、可做权限检查的常规文件路径:
- 套接字没有对应的文件系统路径,内核找不到可检查权限的目标,因此直接允许通过文件描述符对应的
/proc/self/fd/N访问。 - 已删除文件的原路径已经失效,内核无法定位到原文件的inode来做路径权限检查,这时会直接使用文件描述符关联的inode权限(而你原本就持有该文件的打开权限),因此可以正常访问。
这类场景不存在“重新检查原路径权限”的前提,所以能绕过常规的路径权限校验,直接通过/proc/self/fd/N访问。
内容的提问来源于stack exchange,提问作者Helmut Grohne

