Linux – Why does a process of a binary with only execute permission remain hidden in “ps” when using hidepid=2, if the user is not root

linuxprocprocessps

I'm using hidepid=2 to mount /proc, so users can't see any but their own processes. A particular binary I want to use has been restricted to rwx–x–x permissions, so only its owner (root) can read it but other users can execute it. A normal user can run it without problems, but can't see the process with "ps". If the binary has its permissions changed so the user can read it, then the process appears in "ps" again.

A reproducible example:

sudo mount -o remount,hidepid=2 /proc
sudo cp $(which yes) /tmp
sudo chmod 0711 /tmp/yes
/tmp/yes >/dev/null &
ps aux | grep yes # The process is hidden
sudo ps aux | grep yes # The process can be seen by root
kill %1
sudo chmod og+r /tmp/yes
/tmp/yes >/dev/null &
ps aux | grep yes # The process appears in the list

Why is this happening? It obviously has some relationship to the file permissions, but it shouldn't have: if the process belongs to a user, the user should be able to see it even if the binary is restricted.

My guess is that, as the link "exe" inside /proc/PID points to the binary being executed, the kernel is forbidding all access the the directory in addition to the binary itself. But I'd like to know if this is true or just a consequence of some other thing going on.

Thanks in advance!

Best Answer

The answer is (or at least starts) in fs/proc/base.c (unchanged from kernel 3.12 to 4.2 at least)

742 static int proc_pid_permission(struct inode *inode, int mask)
743 {
744         struct pid_namespace *pid = inode->i_sb->s_fs_info;
745         struct task_struct *task;
746         bool has_perms;
747 
748         task = get_proc_task(inode);
749         if (!task)
750                 return -ESRCH;
751         has_perms = has_pid_permissions(pid, task, 1);
752         put_task_struct(task);
753 
754         if (!has_perms) {
755                 if (pid->hide_pid == 2) {
756                         /*
757                          * Let's make getdents(), stat(), and open()
758                          * consistent with each other.  If a process
759                          * may not stat() a file, it shouldn't be    seen
760                          * in procfs at all.
761                          */
762                         return -ENOENT;
763                 }
764 
765                 return -EPERM;
766         }
767         return generic_permission(inode, mask);
768 }

The code above is the starting point for determining if a specific /proc/PID entry can been seen to exist or not. When hide_pid is set to 2 it returns -ENOENT if you don't have the required permission. Permissions are checked via:

has_pid_permissions()ptrace_may_access()__ptrace_may_access()

__ptrace_may_access() denies access because the process is not "dumpable" as it was created from an unreadable executable image, as determined during process creation:

setup_new_exec()would_dump()

1118 void would_dump(struct linux_binprm *bprm, struct file *file)
1119 {
1120         if (inode_permission(file_inode(file), MAY_READ) < 0)
1121                 bprm->interp_flags |= BINPRM_FLAGS_ENFORCE_NONDUMP;
1122 }