Build product paths cannot reference locations outside of the Nix store. We previously disallowed paths from being symlinks, but this didn't take into account that parent path elements can be symlinks as well. So a build product /nix/store/bla…/foo/passwd, with /nix/store/bla…/foo being a symlink to /etc, would still work.
So now we check all paths encountered during path resolution. Symlinks are allowed again so long as they point to the Nix store.
6ZB4CIW66KZMCEBTUWTRRNKQAV5WVPYX4QLFAJT5TTJ3CMS4JMXQC
IJSJLRZHJXAGSY5MWKQWFU2H6VA2EKS3FUAK3DXW6KGXLO3AMS2QC
HTL6HIBMRGSX2H2H7KB4MC3H6UQ5C752VC3UHC43SRA7V66PQCRQC
PY5GVGC7QVTOTCZ52LRHUCUJY5MER6ADCMTC2FR2WQACQV4QV5CQC
7UHHF564BOGHA2CP4OZA3BEPUUTY5S3SS53A32F7RHEVSIDEVWRAC
LBNVQXUBEZ45SOTGVXK5UEZXIAIZTJLWZNUYFI4JZ6J65N3KPDVQC
XJFHFZCA2VXGYSRDJDLAZZX3MLRH2KDT242DHWRU5XQRJ6XIHZUQC
GJFYEU3SVP7TDSYXVZEYGKN4NVWSZX4754PPPTOYPRHUO5RMDWPQC
PMNWRTGJ4GVSMSSAWSUD57B26PCRAHMZIQ5SIWJIK7A74ENKEQLAC
OOQ2D3KCLFPYNAN253PHWLBQMB6OMO2KYQWQXLTP65SQAYZWQ5LAC
YDVFPMKPTZAZTF37O3V3CCMRIZHUMD6QNB6A775ZCKFTCHWRHVZQC
VH5ZABDRP565VZIG55YHNYYPST53NQ2J6YM362NSLXCAHI5WPH4AC
2GK5DOU7ODF4WBSN3QTD3WIO52VTL2LOAXKGCDEMMAQPTEO4A4HAC
BDSD2JLV4V4I52SE7MCZLYNDC4XU27ZNH7TYKLT7CA7YR5WRMQBAC
my $storeDir = $Nix::Config::storeDir . "/";
error($c, "Invalid path in build product.")
if substr($path, 0, length($storeDir)) ne $storeDir || $path =~ /\/\.\./;
error($c, "Path ‘$path’ is a symbolic link.") if -l $path;
my $path = pathIsInsidePrefix($path, $Nix::Config::storeDir);
error($c, "Build product refers outside of the Nix store.") unless defined $path;
return $path;
}
# Check whether ‘$path’ is inside ‘$prefix’. In particular, it checks
# that resolving symlink components of ‘$path’ never takes us outside
# of ‘$prefix’. We use this to check that Nix build products don't
# refer to things outside of the Nix store (e.g. /etc/passwd) or to
# symlinks outside of the store that point into the store
# (e.g. /run/current-system). Return undef or the resolved path.
sub pathIsInsidePrefix {
my ($path, $prefix) = @_;
my $n = 0;
$path =~ s/\/+/\//g; # remove redundant slashes
$path =~ s/\/*$//; # remove trailing slashes
return undef unless $path eq $prefix || substr($path, 0, length($prefix) + 1) eq "$prefix/";
my @cs = File::Spec->splitdir(substr($path, length($prefix) + 1));
my $cur = $prefix;
foreach my $c (@cs) {
next if $c eq ".";
# ‘..’ should not take us outside of the prefix.
if ($c eq "..") {
return if length($cur) <= length($prefix);
$cur =~ s/\/[^\/]*$// or die; # remove last component
next;
}
my $new = "$cur/$c";
if (-l $new) {
my $link = readlink $new or return undef;
$new = substr($link, 0, 1) eq "/" ? $link : "$cur/$link";
$new = pathIsInsidePrefix($new, $prefix);
return undef unless defined $new;
}
$cur = $new;
}
return $cur;