#! /usr/bin/perl -w -I /home/eelco/.nix-profile/lib/site_perl use strict; use XML::LibXML; #use XML::Simple; my $blacklistFN = shift @ARGV; die unless defined $blacklistFN; my $userEnv = shift @ARGV; die unless defined $userEnv; # Read the blacklist. my $parser = XML::LibXML->new(); my $blacklist = $parser->parse_file($blacklistFN)->getDocumentElement; #print $blacklist->toString() , "\n"; # Get all the elements of the user environment. my $userEnvElems = `nix-store --query --references '$userEnv'`; die "cannot query user environment elements" if $? != 0; my @userEnvElems = split ' ', $userEnvElems; my %storePathHashes; sub getElemNodes { my $node = shift; my @elems = (); foreach my $node ($node->getChildNodes) { push @elems, $node if $node->nodeType == XML_ELEMENT_NODE; } return @elems; } my %referencesCache; sub getReferences { my $path = shift; return $referencesCache{$path} if defined $referencesCache{$path}; my $references = `nix-store --query --references '$path'`; die "cannot query references" if $? != 0; $referencesCache{$path} = [split ' ', $references]; return $referencesCache{$path}; } my %attrsCache; sub getAttr { my $path = shift; my $name = shift; my $key = "$path/$name"; return $referencesCache{$key} if defined $referencesCache{$key}; my $value = `nix-store --query --binding '$name' '$path' 2> /dev/null`; $value = "" if $? != 0; # !!! chomp $value; $referencesCache{$key} = $value; return $value; } sub evalCondition; sub traverse { my $done = shift; my $set = shift; my $path = shift; my $stopCondition = shift; return if defined $done->{$path}; $done->{$path} = 1; $set->{$path} = 1; # print " in $path\n"; if (!evalCondition({$path => 1}, $stopCondition)) { # print " STOPPING in $path\n"; return; } # Get the requisites of the deriver. foreach my $reference (@{getReferences $path}) { traverse($done, $set, $reference, $stopCondition); } } sub evalSet { my $inSet = shift; my $expr = shift; my $name = $expr->getName; if ($name eq "traverse") { my $stopCondition = (getElemNodes $expr)[0]; my $done = { }; my $set = { }; foreach my $path (keys %{$inSet}) { traverse($done, $set, $path, $stopCondition); } return $set; } else { die "unknown element `$name'"; } } # Function for evaluating conditions. sub evalCondition { my $storePaths = shift; my $condition = shift; my $elemName = $condition->getName; if ($elemName eq "containsSource") { my $hash = $condition->attributes->getNamedItem("hash")->getValue; foreach my $path (keys %{$storePathHashes{$hash}}) { return 1 if defined $storePaths->{$path}; } return 0; } elsif ($elemName eq "hasName") { my $nameRE = $condition->attributes->getNamedItem("name")->getValue; foreach my $path (keys %{$storePaths}) { return 1 if $path =~ /$nameRE/; } return 0; } elsif ($elemName eq "hasAttr") { my $name = $condition->attributes->getNamedItem("name")->getValue; my $valueRE = $condition->attributes->getNamedItem("value")->getValue; foreach my $path (keys %{$storePaths}) { if ($path =~ /\.drv$/) { my $value = getAttr($path, $name); # print " $path $name $value\n"; return 1 if $value =~ /$valueRE/; } } return 0; } elsif ($elemName eq "and") { my $result = 1; foreach my $node (getElemNodes $condition) { $result &= evalCondition($storePaths, $node); } return $result; } elsif ($elemName eq "not") { return !evalCondition($storePaths, (getElemNodes $condition)[0]); } elsif ($elemName eq "within") { my @elems = getElemNodes $condition; my $set = evalSet($storePaths, $elems[0]); return evalCondition($set, $elems[1]); } elsif ($elemName eq "true") { return 1; } elsif ($elemName eq "false") { return 0; } else { die "unknown element `$elemName'"; } } sub evalOr { my $storePaths = shift; my $nodes = shift; my $result = 0; foreach my $node (@{$nodes}) { $result |= evalCondition($storePaths, $node); } return $result; } # Iterate over all elements, check them. foreach my $userEnvElem (@userEnvElems) { # Get the deriver of this path. my $deriver = `nix-store --query --deriver '$userEnvElem'`; die "cannot query deriver" if $? != 0; chomp $deriver; if ($deriver eq "unknown-deriver") { # print " deriver unknown, cannot check sources\n"; next; } print "CHECKING $userEnvElem\n"; # Get the requisites of the deriver. # my $requisites = `nix-store --query --requisites --include-outputs '$deriver'`; # die "cannot query requisites" if $? != 0; # my @requisites = split ' ', $requisites; # Get the hashes of the requisites. # my $hashes = `nix-store --query --hash @requisites`; # die "cannot query hashes" if $? != 0; # my @hashes = split ' ', $hashes; # for (my $i = 0; $i < scalar @requisites; $i++) { # die unless $i < scalar @hashes; # my $hash = $hashes[$i]; # $storePathHashes{$hash} = {} unless defined $storePathHashes{$hash}; # my $r = $storePathHashes{$hash}; # !!! fix # $$r{$requisites[$i]} = 1; # } # Evaluate each blacklist item. foreach my $item ($blacklist->getChildrenByTagName("item")) { my $itemId = $item->getAttributeNode("id")->getValue; # print " CHECKING FOR $itemId\n"; my $condition = ($item->getChildrenByTagName("condition"))[0]; die unless $condition; # Evaluate the condition. my @elems = getElemNodes $condition; if (evalOr({$deriver => 1}, \@elems)) { # Oops, condition triggered. my $reason = ($item->getChildrenByTagName("reason"))[0]->getChildNodes->to_literal; $reason =~ s/\s+/ /g; $reason =~ s/^\s+//g; print " VULNERABLE TO `$itemId': $reason\n"; } } }