Consider running the following Python code as root:
import os
f=os.open("/etc/shadow", os.O_RDONLY)
os.setuid(65535)
os.open(f"/proc/self/fd/{f}", os.O_RDONLY)
Here is a one-liner convenient for pasting:
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)'
Given the comment of proc_fd_permission, I would expect this code to succeed. However, I actually observe -EACCES. Why is this use of /proc/self/fd/N not permitted and what is the source code comment actually trying to convey?
Update: If the permission only applies to the symlink itself and not the target file, why can I open sockets and deleted files via /proc/self/fd/N? (e.g. exec 3>foo; echo hello >&3; rm foo; cat /proc/self/fd/3 prints hello)
1 Answer 1
Inside your program, open files are represented as integer file descriptors. These are the filenames of the entries in the directory: /proc/self/fd. The kernel's /proc/ ABI helps you debug your system by displaying what those file descriptors refer to using the conventions of symlinks. But those symlinks are not equivalent to the open file descriptor.
$ ls -l /proc/self/fd
total 0
lrwx------. 1 tinkerer tinkerer 64 Mar 10 08:19 0 -> /dev/pts/0
lrwx------. 1 tinkerer tinkerer 64 Mar 10 08:19 1 -> /dev/pts/0
lrwx------. 1 tinkerer tinkerer 64 Mar 10 08:19 2 -> /dev/pts/0
lr-x------. 1 tinkerer tinkerer 64 Mar 10 08:19 3 -> /proc/230669/fd
If you close a file descriptor, there is no guarantee you can open it again. You have to satisfy the kernel's permission model to do that at the time you attempt to open the file. When you change UID, you should expect the permission model to view the open request differently.
If you want a second file descriptor to point to an already open file (descriptor), you should use the os.dup() method.
1 Comment
/proc/self/fd/... both of which are in fact possible. As such that statement is relatively obviously wrong and with it, your answer becomes relatively useless. It also is obvious that dup2 is the way to go if possible. That's not the question about that. It just is quite hard to shoehorn dup2 into existing ELF binaries. Hence the question of "fooling" open.Explore related questions
See similar questions with these tags.
f"/proc/self/fd/{f}"will be a symbolic link to/etc/shadowwhich the process no longer has permission to open once it has dropped its privileges.link=os.readlink(f"/proc/self/fd/{f}"); print(link)but that does not grant you permission to open the file that the link points to.-EACCESis the natural outcome. But what is the benefit ofproc_fd_permissionand its special handling? If the link is just followed, then how does opening a deleted file work? Considerexec 3>foo; echo hello >&3; rm foo; cat /proc/self/fd/3which will printhello.proc_fd_permissionis applied to inodes of directories/proc/PID/fd, not inodes of files/symlinks/proc/PID/fd/*, you got-EACCESmost likely due toproc_fd_access_allowed(elixir.bootlin.com/linux/latest/source/fs/proc/base.c#L1752). You still can list files in directory/proc/self/fdafteros.setuid(65535), that's the purpose ofproc_fd_permission. it is setup here (elixir.bootlin.com/linux/latest/source/fs/proc/base.c#L3591)https://elixir.bootlin.com/linux/latest/source/fs/proc/base.c#L672and that we requireptracecapability. That raises the next question: Why is my process unable to ptrace itself?