*stat()-ing symlinks with trailing slashes

Mircea Danila Dumitrescu venatir at xss.ro
Fri May 22 21:16:34 UTC 2009


Hi all,

Basically here it's what happens:
------------
macmac:test123 venatir$ >./file
macmac:test123 venatir$ stat ./file
234881026 4995340 -rw-r--r-- 1 venatir wheel 0 0 "May 22 21:35:18  
2009" "May 22 21:35:18 2009" "May 22 21:35:18 2009" "May 22 21:35:18  
2009" 4096 0 0 ./file
macmac:test123 venatir$ stat ./file/
stat: ./file/: stat: Not a directory
macmac:test123 venatir$ ln -s ./file ./link
macmac:test123 venatir$ stat ./link
234881026 4995349 lrwxr-xr-x 1 venatir wheel 0 6 "May 22 21:35:36  
2009" "May 22 21:35:36 2009" "May 22 21:35:36 2009" "May 22 21:35:36  
2009" 4096 8 0 ./link
macmac:test123 venatir$ stat ./link/
234881026 4995340 -rw-r--r-- 1 venatir wheel 0 0 "May 22 21:35:18  
2009" "May 22 21:35:18 2009" "May 22 21:35:18 2009" "May 22 21:35:18  
2009" 4096 0 0 ./link/
macmac:test123 venatir$
------------

This is on OS X 10.5.7, but it has the exact same behavior on FreeBSD.
It does not seem to cause so many problems unless some program checks  
if a path is a directory or a file.
Lighttpd checks a link with a trailing slash and it thinks it's a  
directory, because it does not get ENOTDIR as a normal file returns.  
Of course, if you read what stat returns, you can see it is not a  
directory, but it's an inconsistency between links and normal files  
and they started to put linux on a pedestal :P, which hurts my BSD  
pride.
The final behavior for the web server is that you have any cgi script  
(php, ruby, bash or even an elf file) and that script is a link, you  
can just put a trailing slash after it and get the source.
And I bet there are other affected software out there, but we just  
have not discovered the bugs ... yet.

Mircea Danila Dumitrescu
IT Contractor
XSS Consultancy LTD
venatir at xss.ro
m: 00447543670304
m: 00447904550241



On 22 May 2009, at 20:58, Vlad GALU wrote:

> On 5/22/09, Vlad GALU <dudu at dudu.ro> wrote:
>> -- cut here --
>> root at goofy / # rm -f passwd
>> root at goofy / # ln -s /etc/passwd passwd
>> root at goofy / # stat passwd
>> 74 3 lrwxr-xr-x 1 root wheel 1668572463 11 "May 22 19:34:17 2009"  
>> "May
>> 22 19:34:17 2009" "May 22 19:34:17 2009" "May 22 19:34:17 2009"  
>> 4096 0
>> 0 passwd
>> root at goofy / # stat passwd/
>> 74 95688 -rw-r--r-- 1 root wheel 393192 2158 "May 21 09:27:10 2009"
>> "May 21 09:27:10 2009" "May 22 17:25:49 2009" "Apr  7 13:05:32 2008"
>> 4096 8 0 passwd/
>> root at goofy / #
>> -- and here --
>>
>> stat(1) is smart enough to figure out that my /passwd is a symlink
>> then calls lstat() on it, thus returning the struct stat  
>> corresponding
>> to /etc/passwd
>> However, there's http://www.freebsd.org/cgi/query-pr.cgi?pr=kern/ 
>> 21768
>>
>> vfs_lookup.c has this piece of code:
>> -- cut here --
>>        /*
>>         * Check for bogus trailing slashes.
>>         */
>>        if (trailing_slash && dp->v_type != VDIR) {
>>                error = ENOTDIR;
>>                goto bad2;
>>        }
>> -- and here --
>>
>> I've CC-ed my friend Mircea Danila, who noticed this behavior with  
>> lighttpd.
>> As my friend Mircea Danila, who I've CC-ed found out, lighttpd  
>> mistakenly treats
>>
>
> So, to finish my idea, since I wasn't previously able to write a fully
> coherent mail, the behavior is that lighttpd returns the full source
> of scripts, instead of executing them, when they're symlinks and when
> the GET requests has a trailing "/". When there's no trailing slash,
> they get executed, as expected. The lighttpd devs say that, due to
> stat() not returning ENOTDIR, they simply try to list the content.
>
> Unfortunately I haven't dug any deeper into this, but merely proxied
> the symptoms from Mircea to this list. He should be able to provide
> more input on request.



More information about the freebsd-fs mailing list