2008-08-07 06:31:30

by Daryl Tester

[permalink] [raw]
Subject: PROBLEM?: "permission denied" when accessing /proc/self/fd/* after setuid

Howdee - first lkml post, please be gentle. :-)

When a process setuid's to a non root user, under some circumstances
it can no longer open /dev/stdout or /dev/stderr (which symlink to
/proc/self/fd/1 & 2 respectively), which results in EACCESS. Expected
behavior is that the process should be able to open these already
opened filed descriptors, but I'm not sure if this is intended or not.

Am I expecting too much here of /dev/std* and /proc/self/fd/* being
kosher methods to access a process' already open file descriptors?

(Background)

>From a discussion in <http://marc.info/?t=121760392200003&r=1&w=2>
a user was attempting to open /dev/stderr for a log file under a djb
supervised process. The process initially runs as root, then setuid's
to another id. This process is connected to another process (for
logging) on stdout (and dup'd onto stderr) via an anonymous pipe.
The open of /dev/stderr fails with EACCESS.

The above environment isn't necessary to replicate the problem, although
what stdout and stderr are attached to has some effect. The attached C
code replicates the issue, but appears to not fail (that is, succeed) if
/proc/self/fd/2 is a terminal (e.g. /dev/pts/X) *and* that terminal is
owned by the same uid that the code uses (in this case, 500). In the
case of an anonymous pipe it appears to fail consistently as the pipe is
owned by root.

So, for example:

# ls -nl /proc/self/fd/2 /dev/pts/2
crw------- 1 1000 5 136, 2 2008-08-07 15:36 /dev/pts/2
lrwx------ 1 0 0 64 2008-08-07 15:36 /proc/self/fd/2 -> /dev/pts/2
# ./self_fd /proc/self/fd/2
/proc/self/fd/2: uid: 1000, gid: 5
open(/proc/self/fd/2) as uid 500: Permission denied
/proc/self/fd/2: uid: 1000, gid: 5

I've managed to replicate this issue on several platforms - a Centos
5.2 machine running 2.6.18-92, an Ubuntu 8.04 x86_64 running 2.6.24-19,
and an Ubuntu 7.04 running 2.6.20-17 (A FreeBSD 4.11 box works OK
though :-).


Cheers,
--dt


Attachments:
self_fd.c (1.84 kB)

2008-08-07 15:20:31

by Sven Wegener

[permalink] [raw]
Subject: Re: PROBLEM?: "permission denied" when accessing /proc/self/fd/* after setuid

On Thu, 7 Aug 2008, Daryl Tester wrote:

> When a process setuid's to a non root user, under some circumstances
> it can no longer open /dev/stdout or /dev/stderr (which symlink to
> /proc/self/fd/1 & 2 respectively), which results in EACCESS. Expected
> behavior is that the process should be able to open these already
> opened filed descriptors, but I'm not sure if this is intended or not.

If a program wants to fiddle with an existing _file_descriptor_, it should
use the dup syscalls or access the descriptor directly. From my point of
view open() should return a new instance of what you're trying to open and
should not return a clone of something you already have opened in the
past. And actually cloning a file handle during open() means bypassing the
permission checks. Yes, you already have it open, but I think this is
wrong behaviour.

> Am I expecting too much here of /dev/std* and /proc/self/fd/* being
> kosher methods to access a process' already open file descriptors?

Don't know if the behaviour of /dev/std* and /proc/self/fd/* is
standardized in some way. But I guess it's implementation-specific.

> (Background)
>
> From a discussion in <http://marc.info/?t=121760392200003&r=1&w=2>
> a user was attempting to open /dev/stderr for a log file under a djb
> supervised process. The process initially runs as root, then setuid's
> to another id. This process is connected to another process (for
> logging) on stdout (and dup'd onto stderr) via an anonymous pipe.
> The open of /dev/stderr fails with EACCESS.
>
> The above environment isn't necessary to replicate the problem, although
> what stdout and stderr are attached to has some effect. The attached C
> code replicates the issue, but appears to not fail (that is, succeed) if
> /proc/self/fd/2 is a terminal (e.g. /dev/pts/X) *and* that terminal is
> owned by the same uid that the code uses (in this case, 500). In the
> case of an anonymous pipe it appears to fail consistently as the pipe is
> owned by root.

Why should it fail for the terminal? You're the owner of it. For the pipe
you're not, it was created by root in the parent.

> So, for example:
>
> # ls -nl /proc/self/fd/2 /dev/pts/2
> crw------- 1 1000 5 136, 2 2008-08-07 15:36 /dev/pts/2
> lrwx------ 1 0 0 64 2008-08-07 15:36 /proc/self/fd/2 -> /dev/pts/2
> # ./self_fd /proc/self/fd/2
> /proc/self/fd/2: uid: 1000, gid: 5
> open(/proc/self/fd/2) as uid 500: Permission denied
> /proc/self/fd/2: uid: 1000, gid: 5

Works as expected from my point of view. Linux uses symlinks to represent
open files in /proc/self/fd, so when doing the open on /dev/stderr you end
up opening the original file, which requires a permission check. And that
fails for obvious reasons.

> I've managed to replicate this issue on several platforms - a Centos
> 5.2 machine running 2.6.18-92, an Ubuntu 8.04 x86_64 running 2.6.24-19,
> and an Ubuntu 7.04 running 2.6.20-17 (A FreeBSD 4.11 box works OK
> though :-).

Don't have a FreeBSD installation at my fingers, so can't verify. Maybe
they just don't use symlinks to represent the open files, so they do not
end up opening the original file. Just checked Solaris and it looks like

lrwxrwxrwx 1 root root 6 Feb 20 2007 /dev/stdout -> ./fd/1
crw-rw-rw- 1 root root 314, 1 Aug 7 17:10 /dev/fd/1

So you end up opening a device that is world readable and writable, which
then gives you access to your open file descriptor. IIRC that's how it is
implemented historically in UNIX. As said above, don't know if the
behaviour is standardized.

Sven

2008-08-08 00:38:49

by Daryl Tester

[permalink] [raw]
Subject: Re: PROBLEM?: "permission denied" when accessing /proc/self/fd/* after setuid

Sven Wegener wrote:

> If a program wants to fiddle with an existing _file_descriptor_, it should
> use the dup syscalls or access the descriptor directly.

If you have the source to the program, and can (and are allow to) modify
it, then yes I agree, but that isn't always an option. And I guess why
have the interface in the first place if we have dup() and friends?.

> Don't know if the behaviour of /dev/std* and /proc/self/fd/* is
> standardized in some way. But I guess it's implementation-specific.

I guess that's something I was after - if this behavior was defined.
My *belief* (which is questionable) is that it was to emulate dup()
behavior, which it obviously isn't in this case.

>> The above environment isn't necessary to replicate the problem, although
>> what stdout and stderr are attached to has some effect. The attached C
>> code replicates the issue, but appears to not fail (that is, succeed) if
>> /proc/self/fd/2 is a terminal (e.g. /dev/pts/X) *and* that terminal is
>> owned by the same uid that the code uses (in this case, 500). In the
>> case of an anonymous pipe it appears to fail consistently as the pipe is
>> owned by root.

> Why should it fail for the terminal? You're the owner of it.

I didn't expect it to fail, but then I didn't expect it to fail under any
condition. That it's failing for the other conditions is my beef. :-)

> Don't have a FreeBSD installation at my fingers, so can't verify. Maybe
> they just don't use symlinks to represent the open files, so they do not
> end up opening the original file.

According to the man page on my crufty 4.11 box:

If the file descriptor is open and the mode the file is being opened
with is a subset of the mode of the existing descriptor, the call:

fd = open("/dev/fd/0", mode);

and the call:

fd = fcntl(0, F_DUPFD, 0);

are equivalent.

I don't have a more recent box handy, but the man page at
<http://www.freebsd.org/cgi/man.cgi?query=stderr&apropos=0&sektion=0&manpath=FreeBSD+7.0-RELEASE&format=html>
infers that this is still the case.

> As said above, don't know if the behaviour is standardized.

Anybody? Bueller?

Thanks for the response; they warned me this was a high traffic list. :-)


Cheers,
--dt

2008-08-08 00:51:10

by Alexey Dobriyan

[permalink] [raw]
Subject: Re: PROBLEM?: "permission denied" when accessing /proc/self/fd/* after setuid

On Fri, Aug 08, 2008 at 10:08:21AM +0930, Daryl Tester wrote:
>>> The above environment isn't necessary to replicate the problem, although
>>> what stdout and stderr are attached to has some effect. The attached C
>>> code replicates the issue, but appears to not fail (that is, succeed) if
>>> /proc/self/fd/2 is a terminal (e.g. /dev/pts/X) *and* that terminal is
>>> owned by the same uid that the code uses (in this case, 500). In the
>>> case of an anonymous pipe it appears to fail consistently as the pipe is
>>> owned by root.

Something similar was fixed in 2.6.22, but let me check.

2008-08-08 02:25:29

by Daryl Tester

[permalink] [raw]
Subject: Re: PROBLEM?: "permission denied" when accessing /proc/self/fd/* after setuid

Alexey Dobriyan wrote:

> Something similar was fixed in 2.6.22, but let me check.

You're right - from <http://www.kernel.org/pub/linux/kernel/v2.6/ChangeLog-2.6.22>:

commit 8948e11f450e6189a79e47d6051c3d5a0b98e3f3
Author: Alexey Dobriyan <[email protected]>
Date: Tue May 8 00:23:35 2007 -0700

Allow access to /proc/$PID/fd after setuid()

/proc/$PID/fd has r-x------ permissions, so if process does setuid(), it
will not be able to access /proc/*/fd/. This breaks fstatat() emulation
in glibc.

open("foo", O_RDONLY|O_DIRECTORY) = 4
setuid32(65534) = 0
stat64("/proc/self/fd/4/bar", 0xbfafb298) = -1 EACCES (Permission denied)


However, one of the kernels I tried this on (and failed) was a 2.6.24-19 -
is it possible this patch was backed out?

Cheers,
--dt

2008-08-08 02:59:54

by Daryl Tester

[permalink] [raw]
Subject: Re: PROBLEM?: "permission denied" when accessing /proc/self/fd/* after setuid

Daryl Tester wrote:

> Alexey Dobriyan wrote:

>> Something similar was fixed in 2.6.22, but let me check.

Oh, hang on, that's just for directory access. That still
leaves the issue of the open() vs dup() semantics. Nuts.

Cheers,
--dt