Running a program with a specific environment, uid/gid and arguments: withidenvargs
Submitted by Dan Muresan on
In my Tomcat under daemontools hack, I alluded to withidenvargs, a small utility I have written to run a program in a fully-specified environment. This gives you more control than djb's envdir (no newline limitations or '\0' translation hacks in environment variables, plus the ability to specify uid/gid), though in principle I love djb's philosophy of using the filesystem as a parser. Unlike envdir, withidenvargs doesn't inherit the parent's exports.
withidenvargs takes 3 filenames as arguments
- an ids file (newline-separated list of real / effective uid, real gid, effective gid + sgid's — see below) as produced by perl -le '$, = "\n"; print $<, $>, 0+$(, $)'
- an environment file (null-separated list of assignments as produced by env -0)
- an arguments file (null-separated list of arguments starting with the program name, as in C's argv
#!/usr/bin/env perl
use strict; use warnings;
use File::Slurp;
scalar @ARGV == 3 || die;
my ($fid_name, $fenv_name, $farg_name) = @ARGV;
my @ids = read_file ($fid_name);
@ids || die $!; scalar @ids >= 4 || die;
map { chomp } @ids;
my ($uid, $euid, $gid, $egid) = @ids;
$/ = chr (0);
%ENV = ();
open my $fenv, "<", $fenv_name || die $!;
while (<$fenv>) {
my ($k, $v) = split /=/, $_, 2; $ENV {$k} = $v;
chdir ($v) if $k eq "PWD";
}
close ($fenv);
open my $farg, "<", $farg_name || die $!;
my @args = ();
while (<$farg>) { push @args, $_ }
close ($farg);
# handle empty sgid list -- setgroups ([])
$egid = "$egid $egid" unless $egid =~ / /;
if ($uid =~ /[^0-9]/) { # translate to numeric id's if necessary
($uid, $euid) = (getpwnam ($uid), getpwnam ($euid));
$gid = getgrnam ($gid);
$egid = join " ", map { $_ = getgrnam ($_) } (split / /, $egid)
}
$( = "$gid"; $) = "$egid";
$< = $uid; $> = $euid;
#system ("id"); # check
exec @args;
Note: It is important to change the real and effective gid before the uid's, because otherwise the process may “loose root” and be unable to perform privilleged operations.
Also interesting is Perl's way of setting the supplementary group IDs. A process can have, in addition to its read and effective gid, several supplimentary gid's (sgid's); at system level they are controlled using the POSIX getgroups() and the non-POSIX setgroups(). A less-known form of $)-assignment accepts a list of gid's, the first of which signifies the egid, while the rest compose the sgid list. To specify an empty supplimentary list, however, we must repeat the egid (leaving out the second egid changes the meaning of the $)-assignment to egid-only, which would leave the inherited sgid's unchanged, possibly leaking root privilleges to the child process).
As a quirk, withidenvargs picks up the working directory from which it launches the program from the $PWD environment variable. This may or may not be what you want.