I just got Apache and perl running inside a chroot jail; this required
that I make some libraries and also all of /usr/lib/perl5 available
inside the chroot jail.  So I wrote one program to hardlink libraries
into the jail, and another to hardlink all of /usr/lib/perl5.  (Some
people prefer to copy rather than hardlink, due to an unfounded
security concern: if someone gets root inside the chroot jail, they
can scribble all over your system libraries if you hardlink them into
the jail.  However, if someone gets root inside the chroot jail, they
can also escape from the chroot jail.)

Here's the program for hardlinking libraries:

#!/usr/bin/perl -w
use strict;
# hardlink the .so's a particular program needs to start
# into a chroot jail you intend to run that program in

# resolve a path relative to a symlink. probably buggy.
sub relpath {
  my ($base, $relative) = @_;
  return $relative if $relative =~ m|^/|;
  $base =~ s|/[^/]*$||;
  my $result = "$base/$relative";
  1 while ($result =~ s:/\./:/:g or $result =~ s:/[^/]+/\.\.\/:/:);
  return $result;
}

sub test_relpath {
  my ($expected, @args) = @_;
  my $real = relpath @args;
  die "get $real, expected $expected, for (@args)" if $real ne $expected;
}

test_relpath "/foo/baz", "/foo/bar", "baz";
test_relpath "/baz", "/foo/bar", "../baz";
test_relpath "/blork", "/foo/bar", "/blork";

# find the real pathname corresponding to a file which might be a
# symlink
sub pierce_veils {
  my ($file) = @_;
  $file = relpath $file, readlink $file while -l $file;
  return $file;
}

# emit the command to symlink a library into the chroot
sub link_in {
  my ($chrootdir, $dependency) = @_;
  my $reality = pierce_veils $dependency;
  print "sudo ln $reality $chrootdir$dependency\n";
}

# find the libraries a program depends on.
sub dependencies {
  my ($program) = @_;
  open LDD, "ldd $program|";
  my @results;
  while (<LDD>) {
    push @results, $1 if / => (.*) \(/;
  }
  close LDD or die "Couldn't popen ldd: $!";
  return @results;
}

my ($chrootdir, $program) = @ARGV;
die "Usage: $0 chrootdir program" unless defined $program;
foreach my $dependency (dependencies $program) {
  link_in $chrootdir, $dependency unless -e "$chrootdir/$dependency";
}
__END__

And here's the program for hardlinking entire directory trees:
#!/usr/bin/perl -w
use strict;

# This program mirrors a directory tree in another location so as to
# make the files in it available (read-only) inside a chroot.  It's a
# bit excessively paranoid --- it refuses to link in files that are
# owned by other than root --- but it worked for my purposes.  (I just
# wanted to get perl running in a chroot, so I used this program to
# mirror /usr/bin/perl5 in the chroot.)

sub makedirs {
  my ($dir) = @_;
  my @chunks = split /\//, $dir;
  foreach my $i (1..$#chunks) {
    my $name = join '/', @chunks[0..$i];
    if (not -d $name) {
      mkdir $name, 0755 or die "Can't mkdir $name: $!";
    }
  }
}

sub mirrordir {
  my ($src, $dest) = @_;
  lstat $src;
  my $mode = (stat _)[2];
  my $owner = (stat _)[4];
  if ((not -l _ and $mode & 06022) or $owner) { 
    # group or world writable or non-root-owned
    die "file $src doesn't look safe to hardlink into a chroot: " .
      sprintf "mode %o, owner %d", $mode, $owner;
  } elsif (-l _) {
    symlink readlink($src), $dest unless lstat $dest;
  } elsif (-d _) {
    makedirs $dest;
    opendir FOO, $src or die "Can't opendir $src: $!";
    my @filenames = readdir FOO;
    closedir FOO;
    foreach my $file (@filenames) {
      next if $file eq '.' or $file eq '..';
      mirrordir("$src/$file", "$dest/$file");
    }
  } elsif (-f _) {
    link $src, $dest unless lstat $dest;
  } else {
    die "Can't deal with $src";
  }
}

my ($src, $dest) = @ARGV;
die "Usage: $0 sourcedir destdir" if not defined $dest;
mirrordir($src, $dest);
__END__

Reply via email to