Why does “sudo ls -l /proc/1/fd/*” fail?

I usually write about PostgreSQL, but lately someone asked for help, and one of the problems was similar to sudo command from title.

This was not the first time I saw it, so figured, I'll write a blogpost about it, just so I can refer people to it in the future.

First, let's see how it fails, and then we can diagnose the problem:

=$ ls -dl /proc/1/fd
dr-x------ 2 root root 0 Feb 19 13:24 /proc/1/fd/
 
=$ ls -dl /proc/1/fd/*
ls: cannot access '/proc/1/fd/*': Permission denied
 
=$ sudo ls -dl /proc/1/fd/*
ls: cannot access '/proc/1/fd/*': No such file or directory

It looks that I can't list file descriptors for process 1, even from root. But is it really the case? Let's see:

=$ sudo su -
 
root@krowka:~# ls -dl /proc/1/fd/*
lrwx------ 1 root root 64 lut 19 13:24 /proc/1/fd/0 -> /dev/null
lrwx------ 1 root root 64 lut 19 13:24 /proc/1/fd/1 -> /dev/null
lr-x------ 1 root root 64 lut 20 13:42 /proc/1/fd/10 -> /proc/1/mountinfo
...
lrwx------ 1 root root 64 lut 20 13:42 /proc/1/fd/97 -> socket:[482049]

Well, clearly – root can list these files. So why sudo doesn't work?

The answer is pretty simple. ls command doesn't actually support glob operators (like *, ?, or […]).

How doesn't it support, while I can easily see that it does:

=$ ls -ld /b*
drwxr-xr-x 2 root root 12288 Feb  2 07:39 /bin/
drwxr-xr-x 4 root root  4096 Feb 10 12:23 /boot/

Well, in reality, the glob expression (like /b* above) is done by your shell. I can show it using very simple trick. This command:

perl -e 'printf "%2d: [%s]\n", $_+1, $ARGV[$_] for 0..$#ARGV'

If you will give it any arguments, it will print these arguments, with argument number. For example:

=$ perl -e 'printf "%2d: [%s]\n", $_+1, $ARGV[$_] for 0..$#ARGV' hubert depesz lubaczewski
 1: [hubert]
 2: [depesz]
 3: [lubaczewski]

Now, if I'll pass it glob expression for something that I have access to (at least to seeing that it's there), it will get expanded and each file/directory path will be given as separate argument:

=$ perl -e 'printf "%2d: [%s]\n", $_+1, $ARGV[$_] for 0..$#ARGV' /b*
 1: [/bin]
 2: [/boot]

But, if my account doesn't have privileges to list the files, glob expression will be kept there:

=$ perl -e 'printf "%2d: [%s]\n", $_+1, $ARGV[$_] for 0..$#ARGV' /proc/1/fd/*
 1: [/proc/1/fd/*]

Since sudo doesn't do any magic, just runs given command using elevated privileges, it will run ls with root privileges, but instead of list of files, there will be glob expression, that ls can't process.

So, what can you do in such case? The workaround is simple, but not really nice:

=$ sudo bash -c "ls -ld /proc/1/fd/*"
lrwx------ 1 root root 64 Feb 19 13:24 /proc/1/fd/0 -> /dev/null
lrwx------ 1 root root 64 Feb 19 13:24 /proc/1/fd/1 -> /dev/null
lr-x------ 1 root root 64 Feb 20 13:42 /proc/1/fd/10 -> /proc/1/mountinfo
lr-x------ 1 root root 64 Feb 20 13:42 /proc/1/fd/11 -> anon_inode:inotify
...
lrwx------ 1 root root 64 Feb 20 13:42 /proc/1/fd/97 -> socket:[482049]

Please note that I had to put whole command (ls -ld …) inside quotation marks – otherwise each element would be passed as separate option to bash, which wouldn't result in sensible result:

=$ sudo bash -c ls -ld /proc/1/fd/*
a  b  c

a, b, and c are files in current directory.

Alternatively you can use find – which does support globs, and even more stuff:

=$ sudo find /proc/1/fd -ls
    11342      0 dr-x------   2 root     root            0 Feb 19 13:24 /proc/1/fd
    11343      0 lrwx------   1 root     root           64 Feb 19 13:24 /proc/1/fd/0 -> /dev/null
    11344      0 lrwx------   1 root     root           64 Feb 19 13:24 /proc/1/fd/1 -> /dev/null
    11345      0 lrwx------   1 root     root           64 Feb 19 13:24 /proc/1/fd/2 -> /dev/null
    11346      0 l-wx------   1 root     root           64 Feb 19 13:24 /proc/1/fd/3 -> /dev/kmsg
...
   591992      0 lrwx------   1 root     root           64 Feb 20 13:42 /proc/1/fd/97 -> socket:[482049]

Hope it clears the problem. If anything else is not clear, and you happen to use BASH, then I highly recommend that you read:

  1. BASH Frequently Asked Questions
  2. Bash Pitfalls
  3. Bash Guide

It did teach me a lot, and helped to understand what I was doing wrong when scripting things in Bash.

One thought on “Why does “sudo ls -l /proc/1/fd/*” fail?”

  1. innym powodem dla którego nie działa ‘*’ jako nazw plików mogą być inne opcje powłoki dla konta usera i dla konta root;

    np.
    $ set -f
    $ ls -l *
    ls: nie ma dostępu do *: Nie ma takiego pliku ani katalogu

    $ set +f
    $ ls -l *
    [są pliki]

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.