Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

[perl #24641] Failing POSIX::getuid/getgid and related ops

0 views
Skip to first unread message

Stas Bekman

unread,
Dec 11, 2003, 6:17:43 PM12/11/03
to bugs-bi...@netlabs.develooper.com
# New Ticket Created by Stas Bekman
# Please include the string: [perl #24641]
# in the subject line of all future correspondence about this issue.
# <URL: http://rt.perl.org:80/rt3/Ticket/Display.html?id=24641 >


Jörg Walter was helping me over irc to debug some POSIX uid/gid
get/set related problems. Here are the bugs that we have identified:

All the following tests are run as root. We will attempt to
setuid/setgid into user 'nobody' and then try to check whether that
user is allowed to read/write/execute in that directory. The directory
/root has the perm:

drwx------ root root

so it should fail to do that.

Test 1:

Using perl's $(, $), $<, $> "sudo" to "nobody" and try to check
whether /root is -r, -w, -x by 'nobody':

% perl -le ' \
my($uid, $gid) = (getpwnam("nobody"))[2..3]; \
$( = $) = $gid+0; \
die "failed to change gid to $gid (now: $(, $))" if $( != $gid; \
$< = $> = $uid+0; \
die "failed to change uid to $uid (now: $<, $>)" if $< != $uid; \
print -r q{/root} && -w _ && -x _ ? q{OK} : q{NOK};'
NOK

as expected user 'nobody' can't -r, -w, -x '/root'. Notice that set
uid and set gid (and their effective counterparts) process was
successful (as we've got NOK).

Test 2:

Do the same this time using the equivalent POSIX calls:

% perl -le 'require POSIX; \
my($uid, $gid) = (getpwnam("nobody"))[2..3]; \
POSIX::setgid($gid+0); \
die "failed to change gid to $gid (now: $(, $))" \
if POSIX::getgid() != $gid; \
POSIX::setuid($uid+0); \
die "failed to change uid to $uid (now: $<, $>)" \
if POSIX::getuid() != $uid; \
print -r q{/root} && -w _ && -x _ ? q{OK} : q{NOK};'
failed to change gid to 65534 (now: 0 0, 0 0) at -e line 4.

oops, has setgid failed? Not really, see the next test

Test 3:

Now don't check whether set[ug]id has failed:

% perl -le 'require POSIX; \
my($uid, $gid) = (getpwnam("nobody"))[2..3]; \
POSIX::setgid($gid+0); \
POSIX::setuid($uid+0); \
print -r q{/root} && -w _ && -x _ ? q{OK} : q{NOK};'
OK

Something is wrong as user 'nobody' should get 'NOK' (it can't read
that dir). As Jörg has figured out there are two problems here. If you
look at ps(1) (by adding sleep(100 to keep it up), the above script
shows that the program is running under nobody.nobody, so set[ug]id
didn't fail.

The first problem is that POSIX::getuid and POSIX::getgid return wrong
values (they return 'root' values) as if the process hasn't called
set[ug]id. Changing POSIX set calls to perl vars seems to affect
POSIX::get[ug]id:

% perl -le 'require POSIX; \
my($uid, $gid) = (getpwnam("nobody"))[2..3]; \
$( = $) = $gid+0; \
die "failed to change gid to $gid (now: $(, $))" \
if POSIX::getgid() != $gid; \
$< = $> = $uid+0; \
die "failed to change uid to $uid (now: $<, $>)" \
if POSIX::getuid() != $uid; \
print -r q{/root} && -w _ && -x _ ? q{OK} : q{NOK};'
NOK

meaning that POSIX::set[ug]id do affect the program environment, but
not perl variables $(, $), $<, $> and not POSIX::get[ug]id.

Now look again at the test #3, it has reported OK, even though the
process has successfully migrated to run under 'nobody'. Let's change
the test to do something that is outside of perl's control, e.g. try
to write to that directory, and expect it to fail under nobody:

% perl -le 'require POSIX; \
my($uid, $gid) = (getpwnam("nobody"))[2..3]; \
POSIX::setgid($gid+0); \
POSIX::setuid($uid+0); \
qx[each "foo" > /root/nobody]'
sh: line 1: /root/nobody: Permission denied

which shows what? That directives -r, -w, -x still think that the
process is runnin under 'root' and return OK.

So to summarize there are two bugs: while doing what it is supposed to do
POSIX::set[ug]id doesn't affect
1) $(, $), $<, $>, POSIX::get[ug]id.
2) -r, -w, -x ops
may be it's the same issue.

These aren't glibc bugs, since this C program works correctly (its get

#include <stdio.h>
main() {
setgid(65534);
setuid(65534);
printf("%i %i\n",getuid(),getgid());
}

get calls match the set calls. (65534 is nobody on my machine)

I can see the same behavior with all perls I have 5.005_03 - blead,
here is one of them:

perl -V
Summary of my perl5 (revision 5.0 version 8 subversion 1) configuration:
Platform:
osname=linux, osvers=2.4.18-23mdksmp, archname=i386-linux-thread-multi
uname='linux hp6.mandrakesoft.com 2.4.18-23mdksmp #1 smp fri aug 2
12:31:40 cest 2002 i686 unknown unknown gnulinux '
config_args='-des -Dinc_version_list=5.8.0/i386-linux-thread-multi 5.8.0
5.6.1 5.6.0 -Darchname=i386-linux -Dcc=gcc -Doptimize=-O2 -fomit-frame-pointer
-pipe -march=i586 -mcpu=pentiumpro -Dprefix=/usr -Dvendorprefix=/usr
-Dsiteprefix=/usr -Dman3ext=3pm -Dcf_by=MandrakeSoft -Dmyhostname=localhost
-Dperladmin=root@localhost -Dd_dosuid -Ud_csh -Duseshrplib -Dusethreads'
hint=recommended, useposix=true, d_sigaction=define
usethreads=define use5005threads=undef useithreads=define
usemultiplicity=define
useperlio=define d_sfio=undef uselargefiles=define usesocks=undef
use64bitint=undef use64bitall=undef uselongdouble=undef
usemymalloc=n, bincompat5005=undef
Compiler:
cc='gcc', ccflags ='-D_REENTRANT -D_GNU_SOURCE -DTHREADS_HAVE_PIDS
-fno-strict-aliasing -I/usr/local/include -D_LARGEFILE_SOURCE
-D_FILE_OFFSET_BITS=64 -I/usr/include/gdbm',
optimize='-O2 -fomit-frame-pointer -pipe -march=i586 -mcpu=pentiumpro ',
cppflags='-D_REENTRANT -D_GNU_SOURCE -DTHREADS_HAVE_PIDS
-fno-strict-aliasing -I/usr/local/include -I/usr/include/gdbm'
ccversion='', gccversion='3.3.1 (Mandrake Linux 9.2 3.3.1-1mdk)',
gccosandvers=''
intsize=4, longsize=4, ptrsize=4, doublesize=8, byteorder=1234
d_longlong=define, longlongsize=8, d_longdbl=define, longdblsize=12
ivtype='long', ivsize=4, nvtype='double', nvsize=8, Off_t='off_t',
lseeksize=8
alignbytes=4, prototype=define
Linker and Libraries:
ld='gcc', ldflags =' -L/usr/local/lib'
libpth=/usr/local/lib /lib /usr/lib
libs=-lnsl -lndbm -lgdbm -ldl -lm -lcrypt -lutil -lpthread -lc
perllibs=-lnsl -ldl -lm -lcrypt -lutil -lpthread -lc
libc=/lib/libc-2.3.2.so, so=so, useshrplib=true, libperl=libperl.so
gnulibc_version='2.3.2'
Dynamic Linking:
dlsrc=dl_dlopen.xs, dlext=so, d_dlsymun=undef, ccdlflags='-rdynamic
-Wl,-rpath,/usr/lib/perl5/5.8.1/i386-linux-thread-multi/CORE'
cccdlflags='-fPIC', lddlflags='-shared -L/usr/local/lib'


Characteristics of this binary (from libperl):
Compile-time options: MULTIPLICITY USE_ITHREADS USE_LARGE_FILES
PERL_IMPLICIT_CONTEXT
Locally applied patches:
RC4
Built under linux
Compiled at Sep 1 2003 17:29:01
%ENV:
PERLDOC_PAGER="less -R"
@INC:
/usr/lib/perl5/5.8.1/i386-linux-thread-multi
/usr/lib/perl5/5.8.1
/usr/lib/perl5/site_perl/5.8.1/i386-linux-thread-multi
/usr/lib/perl5/site_perl/5.8.1
/usr/lib/perl5/site_perl/5.8.0/i386-linux-thread-multi
/usr/lib/perl5/site_perl/5.8.0
/usr/lib/perl5/site_perl
/usr/lib/perl5/vendor_perl/5.8.1/i386-linux-thread-multi
/usr/lib/perl5/vendor_perl/5.8.1
/usr/lib/perl5/vendor_perl/5.8.0/i386-linux-thread-multi
/usr/lib/perl5/vendor_perl/5.8.0
/usr/lib/perl5/vendor_perl
.

__________________________________________________________________
Stas Bekman JAm_pH ------> Just Another mod_perl Hacker
http://stason.org/ mod_perl Guide ---> http://perl.apache.org
mailto:st...@stason.org http://use.perl.org http://apacheweek.com
http://modperlbook.org http://apache.org http://ticketmaster.com

Rafael Garcia-Suarez

unread,
Dec 20, 2003, 5:32:29 PM12/20/03
to perl5-...@perl.org
Stas Bekman (via RT) wrote:
> The first problem is that POSIX::getuid and POSIX::getgid return wrong
> values (they return 'root' values) as if the process hasn't called
> set[ug]id. Changing POSIX set calls to perl vars seems to affect
> POSIX::get[ug]id:
>
> % perl -le 'require POSIX; \
> my($uid, $gid) = (getpwnam("nobody"))[2..3]; \
> $( = $) = $gid+0; \
> die "failed to change gid to $gid (now: $(, $))" \
> if POSIX::getgid() != $gid; \
> $< = $> = $uid+0; \
> die "failed to change uid to $uid (now: $<, $>)" \
> if POSIX::getuid() != $uid; \
> print -r q{/root} && -w _ && -x _ ? q{OK} : q{NOK};'
> NOK
>
> meaning that POSIX::set[ug]id do affect the program environment, but
> not perl variables $(, $), $<, $> and not POSIX::get[ug]id.

Affecting PL_uid et alli from POSIX.xs should be quite easy.
POSIX::get[ui]gd are only functions that return perl variables so this
should solve this issue as well.

...


> which shows what? That directives -r, -w, -x still think that the
> process is runnin under 'root' and return OK.

They internally use PL_uid (most probably).

> So to summarize there are two bugs: while doing what it is supposed to do
> POSIX::set[ug]id doesn't affect
> 1) $(, $), $<, $>, POSIX::get[ug]id.
> 2) -r, -w, -x ops
> may be it's the same issue.

To summarize : easy to fix, but regression tests would be hard to write.

Stas Bekman

unread,
Dec 21, 2003, 10:14:28 PM12/21/03
to Rafael Garcia-Suarez, perl5-...@perl.org
Rafael Garcia-Suarez wrote:
> Stas Bekman (via RT) wrote:
>
>>The first problem is that POSIX::getuid and POSIX::getgid return wrong
>>values (they return 'root' values) as if the process hasn't called
>>set[ug]id. Changing POSIX set calls to perl vars seems to affect
>>POSIX::get[ug]id:
>>
>> % perl -le 'require POSIX; \
>> my($uid, $gid) = (getpwnam("nobody"))[2..3]; \
>> $( = $) = $gid+0; \
>> die "failed to change gid to $gid (now: $(, $))" \
>> if POSIX::getgid() != $gid; \
>> $< = $> = $uid+0; \
>> die "failed to change uid to $uid (now: $<, $>)" \
>> if POSIX::getuid() != $uid; \
>> print -r q{/root} && -w _ && -x _ ? q{OK} : q{NOK};'
>> NOK
>>
>>meaning that POSIX::set[ug]id do affect the program environment, but
>>not perl variables $(, $), $<, $> and not POSIX::get[ug]id.
>
>
> Affecting PL_uid et alli from POSIX.xs should be quite easy.
> POSIX::get[ui]gd are only functions that return perl variables so this
> should solve this issue as well.

Does it mean that you have a fix already? Cool!

>>which shows what? That directives -r, -w, -x still think that the
>>process is runnin under 'root' and return OK.
>
>
> They internally use PL_uid (most probably).
>
>
>>So to summarize there are two bugs: while doing what it is supposed to do
>>POSIX::set[ug]id doesn't affect
>>1) $(, $), $<, $>, POSIX::get[ug]id.
>>2) -r, -w, -x ops
>>may be it's the same issue.
>
>
> To summarize : easy to fix, but regression tests would be hard to write.

Yes, the easiest is to run as root. They could be optional tests, though.

I've also later discovered that:

$( = $) = $gid+0;

is not enough, as it doesn't quite change the group completely. Something
about not running setgroups() (which is supposed to happen internally). If the
/root dir was drwxr-x--- running the above will still allow the inferior owner
access the /root dir rx, via the group perms.

The really right way to drop perms completely is to:

$( = $) = "$gid $gid";


$< = $> = $uid+0;

notice that you pass the same group id for "other groups".

also a few months ago I reported on behalf of one of the modperl users is that
.acls/access() aren't quite working via -r, -w, -x tests on some systems. So
one can't really use -r, -w, -x tests reliably.

Therefore I ended up doing the actual attempts to read, write and execute to
be sure that I get things right:

# this sub is executed from an external process only, since it
# "sudo"'s into a uid/gid of choice
sub run_root_fs_test {
my($uid, $gid, $dir) = @_;

# first must change gid and egid ("$gid $gid" for an empty
# setgroups() call as explained in perlvar.pod)
my $groups = "$gid $gid";
$( = $) = $groups;
die "failed to change gid to $gid" unless $( eq $groups && $) eq $groups;

# only now can change uid and euid


$< = $> = $uid+0;

die "failed to change uid to $uid" unless $< == $uid && $> == $uid;

my $file = catfile $dir, ".apache-test-file-$$-".time.int(rand);
eval "END { unlink q[$file] }";

# unfortunately we can't run the what seems to be an obvious test:
# -r $dir && -w _ && -x _
# since not all perl implementations do it right (e.g. sometimes
# acls are ignored, at other times setid/gid change is ignored)
# therefore we test by trying to attempt to read/write/execute

# -w
open TEST, ">$file" or die "failed to open $file: $!";

# -x
-f $file or die "$file cannot be looked up";
close TEST;

# -r
opendir DIR, $dir or die "failed to open dir $dir: $!";
defined readdir DIR or die "failed to read dir $dir: $!";
close DIR;

# all tests passed
print "OK";
}

If you are wondering what this function is for, it tests whether Apache will
be able to read/write/execute files under dir $dir, once it switches to
$uid/$gid (since Apache is started as root, but never runs its child processes
as such). So hence the test to detect sure failures before they hit the user.

Thanks again to Jörg Walter for working this one out.

p.s. I'm not 100% sure the example in perlsec is correct, as it changes uids
before gids, which I found doesn't quite work (once uid is changed, you can't
change gid of the process). Normally it should be the other way around. Though
it might be different under setuid processes.

Rafael Garcia-Suarez

unread,
Dec 25, 2003, 3:41:41 PM12/25/03
to Stas Bekman, perl5-...@perl.org
Stas Bekman wrote:
> also a few months ago I reported on behalf of one of the modperl users is that
> .acls/access() aren't quite working via -r, -w, -x tests on some systems. So
> one can't really use -r, -w, -x tests reliably.

(I may have mentioned this earlier) Does the filetest pragma help?

Rafael Garcia-Suarez

unread,
Dec 25, 2003, 4:48:59 PM12/25/03
to Stas Bekman, perl5-...@perl.org
Stas Bekman wrote:
> >
> > Affecting PL_uid et alli from POSIX.xs should be quite easy.
> > POSIX::get[ui]gd are only functions that return perl variables so this
> > should solve this issue as well.
>
> Does it mean that you have a fix already? Cool!

I applied the following patch to bleadperl :

(I tried to be the least intrusive possible -- more eyeballs welcome.
Notably this doesn't get around all the mess in mg.c about setting
$>, $< and friends with the forest of system-specific syscalls ; this
isn't the purpose of POSIX.pm after all.)

Change 21958 by rgs@rgs-home on 2003/12/25 21:22:25

Fix bug [perl #24641] : when POSIX::set[ug]id() are called,
update the perl variables PL_uid and PL_euid (resp. PL_gid
and PL_egid) with the new values.

Affected files ...

... //depot/perl/ext/POSIX/POSIX.xs#121 edit

Differences ...

==== //depot/perl/ext/POSIX/POSIX.xs#121 (text) ====

@@ -1827,10 +1827,20 @@
SysRet
setgid(gid)
Gid_t gid
+ CLEANUP:
+ if (RETVAL >= 0) {
+ PL_gid = getgid();
+ PL_egid = getegid();
+ }

SysRet
setuid(uid)
Uid_t uid
+ CLEANUP:
+ if (RETVAL >= 0) {
+ PL_uid = getuid();
+ PL_euid = geteuid();
+ }

SysRetLong
sysconf(name)

Stas Bekman

unread,
Dec 31, 2003, 12:05:55 AM12/31/03
to Rafael Garcia-Suarez, perl5-...@perl.org

The original poster has never followed up on the proposed solution:
http://marc.theaimsgroup.com/?l=apache-modperl&m=105159295409019&w=2
So I don't know.

Let me ping the modperl list, may be someone who has an access to acl based
system can verify that.

0 new messages