#!/usr/bin/env perl # # Clubber -- Less painful chroot environment building and verification. Please # see README.markdown for documentation. Be sure you trust your binaries! This # script won't protect you from ldd exploits. # # @author Tom Ryder # @copyright 2012 Sanctum # # # Force me to write this properly. # use strict; use warnings; # # Import required Perl libraries; these are all pretty standard. # use Cwd qw(abs_path); use Digest::MD5; use File::Basename; use File::Find; use Getopt::Long; # # Ignore stupid and useless messages from File::Find. # no warnings 'File::Find'; # # Check ldd is available. # chomp(my $ldd = `which ldd`); if (!$ldd) { error("Couldn't find ldd in your \$PATH."); } # # Check options. # my ($chroot, $dry) = ("", 0); my $config = GetOptions("chroot=s" => \$chroot, "dry" => \$dry); if ($chroot) { $chroot = abs_path($chroot); if (!-d $chroot) { error("Nominated chroot %s doesn't seem to be a directory.", $chroot); } } elsif ($dry) { error("Doesn't make sense to specify --dry without --chroot."); } # # Check we were passed at least one parameter, otherwise print a helpful # message. # if (!@ARGV) { printf STDOUT "USAGE: ${0} [--chroot] [--dry] binary1 binary2 ... \n"; exit 0; } # # Check that all our parameters are real files, or point to one. # my $binaries = []; foreach my $argument (@ARGV) { $argument = abs_path($argument); if (-f $argument) { push @$binaries, $argument; } else { error("File %s doesn't seem to exist.", $argument); } } # # Run ldd on all the files and slurp all the absolute paths; put them into a # hash to keep things unique. # my $libraries = {}; foreach my $binary (@$binaries) { my $output = [qx/${ldd} ${binary}/]; foreach my $line (@$output) { if ($line =~ m#(/\S*lib\S+)#) { $libraries->{$1} = 1; } } } # # Include all libnss libraries available, because even static binaries depend # on these for reading files like /etc/passwd. I leave importing those files to # you because it's entirely possible you actually intend to have a different # /etc/passwd or /etc/resolv.conf in your chroot environment. Good practice, # even. # # If two of the libraries have the exact same filename, use the one with the # shortest complete path. # my $nsslibs = {}; my $nssfind = sub { my $basename = $_; if ($File::Find::name =~ /libnss.+\.so/) { if (!exists $nsslibs->{$basename} or length($File::Find::name) < length($nsslibs->{$basename})) { $nsslibs->{$basename} = $File::Find::name; } } }; find($nssfind, qw(/lib /usr/lib)); foreach my $nsslib (keys(%$nsslibs)) { $libraries->{$nsslibs->{$nsslib}} = 1; } # # If we have a chroot, we need to figure out what libraries require importing # and which directories require creating. # if ($chroot) { my ($directories, $imports) = ({}, {}); # # First we'll recurse through the list of libraries and flag any # directories that require creation. We won't complicate things by # reproducing any symbolic link structures. # # If the directory does exist, we'll make sure the library is in place # too, and if it is that its md5sum is the same as our root system # library. If it doesn't exist or if it's different, we'll flag it for # overwriting. # foreach my $library (keys(%$libraries)) { my $directory = dirname($library); # # If the directory doesn't exist, flag it for creation. # if (!-d "${chroot}${directory}") { $directories->{$directory} = 1; } # # If the library exists, we need to see if it's the same as our source # library. # if (-f "${chroot}${library}") { # # Get MD5 checksum of source library. # open(my $src, "<", $library) or error("Couldn't read file %s to checksum it.", $library); binmode($src); my $src_checksum = Digest::MD5->new->addfile($src)->hexdigest; close($src); # # Get MD5 checksum of library presently occupying the path to # which we intend to copy this library. # open(my $dst, "<", "${chroot}${library}") or error("Couldn't read file %s to checksum it.", "${chroot}${library}"); binmode($dst); my $dst_checksum = Digest::MD5->new->addfile($dst)->hexdigest; close($dst); # # Compare checksums; if they're different, we need to copy the # library in. # if ($src_checksum ne $dst_checksum) { $imports->{$library} = 1; } # # The library doesn't exist, so we need to copy it in. # } else { $imports->{$library} = 1; } } # # Check there's something for us to do. # if (keys %$directories || keys %$imports) { # # If we're just supposed to print what we do, all the better, do # that and then quit; and for that, we don't require root privileges. # if ($dry) { if (keys %$directories) { printf STDOUT "Create directories:\n"; foreach my $directory (sort(keys(%$directories))) { printf STDOUT " %s\n", "${chroot}${directory}"; } } if (keys %$imports) { printf STDOUT "Copy libraries:\n"; foreach my $import (sort(keys(%$imports))) { printf STDOUT " %s -> %s\n", $import, "${chroot}${import}"; } } # # Otherwise, we'd best get started, and we need root privileges. # } else { # # Bail if we're not root. # if ($< != 0 || $> != 0) { error("You must have root permissions to use the --chroot parameter."); } # # Create directories and import libraries. # foreach my $directory (sort(keys(%$directories))) { system("mkdir -pv ${chroot}${directory}"); } foreach my $import (sort(keys(%$imports))) { system("cp -pv ${import} ${chroot}${import}"); } } # # If there's nothing we need to do, say so. # } else { printf STDOUT "Nothing to do.\n"; } # # If we don't have a chroot, we can just print the list of libraries, and we're # done. # } else { foreach my $library (sort(keys(%$libraries))) { printf STDOUT "%s\n", $library; } } # # And one way or another, we're done. # exit 0; # # Print a usage message and exit with non-zero. # sub usage { my $message = shift @_; printf STDERR "USAGE: ${message}\n", @_; exit 1; } # # Print a usage message and exit with non-zero. # sub error { my $message = shift @_; printf STDERR "ERROR: ${message}\n", @_; exit 1; }