Unable to stat symbolic link to file

statsymlink

Background
I am trying to wrap my head around how html is unable to access a file via a symlink. I performed the following simple experiment to try and gain an understanding of how apache would be trying to stat/access a file pointed to via a simlink but I unable to understand why it didn't work.

As the user jzhu, I have a symlink in my home directory to a directory in root called other which has the file anaconda-ks.cfg which I want to be able to stat/read in vi. However, I am getting the following results when I am running the stat command.

[jzhu@localhost ~]$ stat /home/jzhu/other/anaconda-ks.cfg
stat: cannot stat `/home/jzhu/other/anaconda-ks.cfg': Permission denied

Here is the stat command of the /home/jzhu/other symlink so you can have further clarification on how I am set up.

[jzhu@localhost ~]$ stat /home/jzhu/other
  File: `/home/jzhu/other' -> `/root/other/'
  Size: 12          Blocks: 0          IO Block: 4096   symbolic link
Device: fd00h/64768d    Inode: 43313       Links: 1
Access: (0777/lrwxrwxrwx)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2013-11-03 08:52:20.602126433 +0000
Modify: 2013-11-03 08:52:20.187918394 +0000
Change: 2013-11-03 08:52:20.187918394 +0000

Looking up the man page for lstat I see the following quote:

These functions return information about a file. No permissions are
required on the file itself, but
— in the case of stat() and lstat() — execute (search) permission is required on all of the directo-
ries in path that lead to the file.

From my understand the stat command does not need executable permissions on /root itself because, unlike lstat, the root directory that it starts off with is the current one in which it is run which means it won't use the full path of "/root/other/" but will use the path "/home/jzhu/other/". I confirmed this via strace with the following relevant output below

lstat("/home/jzhu/other/anaconda-ks.cfg", 0x7fff14d5a7c0) = -1 EACCES (Permission denied)

So in order for me to stat this file, all I should need is executable permission on /home /home/jzhu and /home/jzhu/other

As jzhu, I ran a stat on both /home and /home/jzhu to confirm that I had executable permissions. Shown below

[jzhu@localhost ~]$ stat /home/
  File: `/home/'
  Size: 4096        Blocks: 8          IO Block: 4096   directory
Device: fd00h/64768d    Inode: 2278        Links: 4
Access: (0755/drwxr-xr-x)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2013-11-03 08:52:13.797724760 +0000
Modify: 2013-11-04 00:03:45.377150769 +0000
Change: 2013-11-04 00:03:45.377150769 +0000
[jzhu@localhost ~]$ stat /home/jzhu
  File: `/home/jzhu'
  Size: 4096        Blocks: 8          IO Block: 4096   directory
Device: fd00h/64768d    Inode: 43317       Links: 3
Access: (0700/drwx------)  Uid: (  500/    jzhu)   Gid: (  500/    jzhu)
Access: 2013-11-04 00:00:30.809916040 +0000
Modify: 2013-11-03 23:49:54.781060426 +0000
Change: 2013-11-03 23:49:54.781060426 +0000

However I was unable to stat /home/jzhu/other as jzhu:

[jzhu@localhost ~]$ stat /home/jzhu/other/
stat: cannot stat `/home/jzhu/other/': Permission denied

Changing back too and running stat on the directory. I am confused as to why I am unable to stat the directory as jzhu. As can be seen below, other has read and executable permissions on that directory.

[root@localhost jzhu]# stat /home/jzhu/other/
  File: `/home/jzhu/other/'
  Size: 4096        Blocks: 8          IO Block: 4096   directory
Device: fd00h/64768d    Inode: 32674       Links: 2
Access: (0755/drwxr-xr-x)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2013-11-03 08:50:36.935318703 +0000
Modify: 2013-11-03 08:50:35.772737444 +0000
Change: 2013-11-03 08:50:35.772737444 +0000
[root@localhost jzhu]# stat /home/jzhu/other/anaconda-ks.cfg 
  File: `/home/jzhu/other/anaconda-ks.cfg'
  Size: 985         Blocks: 8          IO Block: 4096   regular file
Device: fd00h/64768d    Inode: 43297       Links: 1
Access: (0777/-rwxrwxrwx)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2013-11-03 08:50:35.772737444 +0000
Modify: 2013-11-03 08:50:35.781741701 +0000
Change: 2013-11-03 08:54:50.814196796 +0000

So the two questions I want to ask are the following.

  1. Why am I unable to stat /home/jzhu/other/ even though I have read and executable permission on other in that directory?
  2. Why am I unable to stat the anaconda-ks.cfg file?

Best Answer

Your answer lies in the first output you provided:

$ stat /home/jzhu/other
  File: `/home/jzhu/other' -> `/root/other/'

This shows that /home/jzhu/other is a symlink to /root/other.

So basically to access anything in /home/jzhu/other/ you are going to need access to /root/other. This means that your user must have execute permissions on both /root and /root/other.


Next lets go on to this command:

$ stat /home/jzhu/other/
stat: cannot stat `/home/jzhu/other/': Permission denied

The reason why this fails and the one above works is because of the trailing /. On any command which works with a path, if the final component of that path (eg: other) is a symlink, and the path ends with a trailing /, then any system calls to operate on that path will try to dereference the symlink, and operate on what the symlink points to instead of the symlink itself.


Solutions:

There are 2 possible solutions to this.

1. Change permissions on /root/other:

As mentioned, add execute capability to both /root and /root/other. You can do this with basic or extended filesystem attributes.

  • You can add the jzhu user to the root group (usermod -a -G root jzhu) and add group execute to the path (chmod g+x /root; chmod g+x /root/other).
    This isn't an ideal solution as it grants your access to anything restricted to the root group.

  • Use filesystem ACLs.

    setfacl -m u:jzhu:x /root
    setfacl -R -m u:jzhu:x /root/other
    setfacl -d -R -m u:jzhu:x /root/other
    

    This makes it so that your specific user gets execute access to /root/other and anything inside it.
    It's still not ideal as if these resources are shared, they shouldn't be in root's home directory.

Note that in both these solutions we only grant the execute (x) bit. The execute bit is needed on a directory for a user to access anything inside that directory. However you also need the read (r) bit to be able to list (ls) the directory. Meaning that without the read bit, you'll have to know exactly where in /root/other the files are. If you want to allow read, just change the x in the last 2 setfacl commands to rx (for example, -m u:jzhu:rx).

2. Move the resources outside /root/other.

This is the preferred solution. You can either get rid of the symlink at /home/jzhu/other and create a directory in it's place, or put them in a shared location somewhere else on the system (without knowing what it is, I can't recommend a good location though).

The reason this is the preferred solution is that if these resources are shared among users, then they don't belong to a specific user and shouldn't be in that user's home directory.

Related Question