Commit 1de4e516 authored by Kirk Webb's avatar Kirk Webb

Add taint state tracking for OSes and Nodes.

Emulab can now propagate OS taint traits on to nodes that load these OSes.
The primary reason for doing this is for loading images which
require special treatment of the node.  For example, an OS that has
proprietary software, and which will be used as an appliance (blackbox)
can be marked (tainted) as such.  Code that manages user accounts on such
OSes, along with other side channel providers (console, node admin, image
creation) can key off of these taint states to prevent or alter access.

Taint states are defined as SQL sets in the 'os_info' and 'nodes' tables,
kept in the 'taint_states' column in both.  Currently these sets are comprised
of the following entries:

* usermode: OS/node should only allow user level access (not root)
* blackbox: OS/node should allow no direct interaction via shell, console, etc.
* dangerous: OS image may contain malicious software.

Taint states are inherited by a node from OSes it loads during the OS load
process.  Similarly, they are cleared from nodes as these OSes are removed.
Any taint state applied to a node will currently enforce disk zeroing.

No other tools/subsystems consider the taint states currently, but that will
change soon.

Setting taint states for an OS has to be done via SQL presently.
parent 0d807a22
......@@ -76,6 +76,9 @@ use vars qw(@ISA @EXPORT);
TB_OSID_DESTROY TB_OSID_MIN TB_OSID_MAX
TB_OSID_OSIDLEN TB_OSID_OSNAMELEN TB_OSID_VERSLEN
TB_TAINTSTATE_USERONLY TB_TAINTSTATE_BLACKBOX TB_TAINTSTATE_DANGEROUS
TB_TAINTSTATE_ALL
TB_IMAGEID_READINFO TB_IMAGEID_MODIFYINFO TB_IMAGEID_EXPORT
TB_IMAGEID_CREATE TB_IMAGEID_DESTROY
TB_IMAGEID_ACCESS TB_IMAGEID_MIN TB_IMAGEID_MAX
......@@ -375,6 +378,14 @@ sub TB_OSID_MBKERNEL() { "_KERNEL_"; } # multiboot kernel OSID
sub TB_OSID_FREEBSD_MFS() { "FREEBSD-MFS" };
sub TB_OSID_FRISBEE_MFS() { "FRISBEE-MFS" };
# OS/Node taint states
sub TB_TAINTSTATE_USERONLY() { "useronly"; };
sub TB_TAINTSTATE_BLACKBOX() { "blackbox"; };
sub TB_TAINTSTATE_DANGEROUS() { "dangerous"; };
sub TB_TAINTSTATE_ALL() { (TB_TAINTSTATE_USERONLY(),
TB_TAINTSTATE_BLACKBOX(),
TB_TAINTSTATE_DANGEROUS()); };
# ImageIDs
#
# Clarification:
......
......@@ -3742,5 +3742,144 @@ sub ClrTipAclUrl($)
"where node_id='$node_id'");
}
#
# Check to see if the node is tainted, or tainted in a
# particular way.
#
sub IsTainted($;$)
{
my ($self, $taint) = @_;
my $taint_states = $self->taint_states();
return 0
if (!defined($taint_states) || $taint_states eq "");
# Just looking to see if any taint is applied?
return 1
if (!defined($taint));
# Looking for a specific taint.
return grep {$_ eq $taint} split(',', $taint_states);
}
#
# Get the current set of taint states for the Node
#
sub GetTaintStates($) {
my ($self) = @_;
my $taint_states = $self->taint_states();
return ()
if (!defined($taint_states) || $taint_states eq "");
return split(',', $taint_states);
}
#
# Explicitly set the taint states based on an input array of states.
# Squash any duplicates or empty/undefined entries.
#
sub SetTaintStates($@) {
my ($self, @taint_states) = @_;
my @newtstates = ();
my @validtstates = TB_TAINTSTATE_ALL();
foreach my $tstate (@taint_states) {
next if (!$tstate);
if (!grep {$_ eq $tstate} @validtstates) {
warn "Invalid taint state: $tstate\n";
return -1;
}
if (!grep {$_ eq $tstate} @newtstates) {
push @newtstates, $tstate;
}
}
return 0
if (!@newtstates);
return $self->Update({"taint_states" => join(',', @newtstates)});
}
#
# Add a taint state to the node.
#
sub AddTaint($$)
{
my ($self, $taint) = @_;
if (!grep {$_ eq $taint} TB_TAINTSTATE_ALL()) {
warn "Invalid taint state: $taint\n";
return -1;
}
return 0
if ($self->IsTainted($taint));
my $taint_states = $self->taint_states();
if (!defined($taint_states) || $taint_states eq "") {
$taint_states = $taint;
}
else {
$taint_states .= ",$taint";
}
return $self->Update({"taint_states" => $taint_states});
}
#
# Inherit the taint states from an OS. Take the union with whatever
# taint states are already set for the node.
#
sub InheritTaintStates($$) {
my ($self, $osinfo) = @_;
require OSinfo;
if (!ref($osinfo)) {
my $tmp = OSinfo->Lookup($osinfo);
if (!defined($tmp)) {
warn "Cannot lookup osinfo for $osinfo\n";
return -1;
}
$osinfo = $tmp;
}
my $os_taint_states = $osinfo->taint_states();
return 0
if (!defined($os_taint_states) || $os_taint_states eq "");
my @taint_states = split(',', $os_taint_states);
my $node_taint_states = $self->taint_states();
if ($node_taint_states) {
push @taint_states, split(',', $node_taint_states);
}
return $self->SetTaintStates(@taint_states);
}
#
# Remove a taint state (or all taint states) from the node.
#
sub RemoveTaint($;$)
{
my ($self, $taint) = @_;
return 0
if (!$self->IsTainted($taint));
my $taint_states = $self->taint_states();
return 0
if (!defined($taint_states) || $taint_states eq "");
if (defined($taint)) {
$taint_states = join(',',
grep {$_ ne $taint} split(',', $taint_states));
} else {
$taint_states = "";
}
return $self->Update({"taint_states" => $taint_states});
}
# _Always_ make sure that this 1 is at the end of the file...
1;
......@@ -730,5 +730,115 @@ sub MapToImage($$)
return Image->Lookup($imageid);
}
#
# Check if the OS is tainted, or tainted in a
# particular way.
#
sub IsTainted($;$)
{
my ($self, $taint) = @_;
my $taint_states = $self->taint_states();
return 0
if (!defined($taint_states) || $taint_states eq "");
# Just looking to see if any taint is applied?
return 1
if (!defined($taint));
# Looking for a specific taint.
return grep {$_ eq $taint} split(',', $taint_states);
}
#
# Get the current set of taint states for the OS
#
sub GetTaintStates() {
my ($self) = @_;
my $taint_states = $self->taint_states();
return ()
if (!defined($taint_states) || $taint_states eq "");
return split(',', $taint_states);
}
#
# Explicitly set the taint states based on an input array of states.
# Squash any duplicates or empty/undefined entries.
#
sub SetTaintStates($@) {
my ($self, @taint_states) = @_;
my @newtstates = ();
my @validtstates = TB_TAINTSTATE_ALL();
foreach my $tstate (@taint_states) {
next if (!$tstate);
if (!grep {$_ eq $tstate} @validtstates) {
warn "Invalid taint state: $tstate\n";
return -1;
}
if (!grep {$_ eq $tstate} @newtstates) {
push @newtstates, $tstate;
}
}
return 0
if (!@newtstates);
return $self->Update({"taint_states" => join(',', @newtstates)});
}
#
# Add a taint state to the OS.
#
sub AddTaint($$)
{
my ($self, $taint) = @_;
if (!grep {$_ eq $taint} TB_TAINTSTATE_ALL()) {
warn "Invalid taint state: $taint\n";
return -1;
}
return 0
if ($self->IsTainted($taint));
my $taint_states = $self->taint_states();
if (!defined($taint_states) || $taint_states eq "") {
$taint_states = $taint;
}
else {
$taint_states .= ",$taint";
}
return $self->Update({"taint_states" => $taint_states});
}
#
# Remove a taint state (or all taint states) from the OS.
#
sub RemoveTaint($;$)
{
my ($self, $taint) = @_;
return 0
if (!$self->IsTainted($taint));
my $taint_states = $self->taint_states();
return 0
if (!defined($taint_states) || $taint_states eq "");
if (defined($taint)) {
$taint_states = join(',',
grep {$_ ne $taint} split(',', $taint_states));
} else {
$taint_states = "";
}
return $self->Update({"taint_states" => $taint_states});
}
# _Always_ make sure that this 1 is at the end of the file...
1;
......@@ -2916,6 +2916,7 @@ CREATE TABLE `nodes` (
`uuid` varchar(40) NOT NULL default '',
`reserved_memory` int(10) unsigned default '0',
`nonfsmounts` tinyint(1) NOT NULL default '0',
`taint_states` set('useronly','blackbox','dangerous') default NULL,
PRIMARY KEY (`node_id`),
KEY `phys_nodeid` (`phys_nodeid`),
KEY `node_id` (`node_id`,`phys_nodeid`),
......@@ -3151,6 +3152,7 @@ CREATE TABLE `os_info` (
`mfs` tinyint(4) NOT NULL default '0',
`reboot_waittime` int(10) unsigned default NULL,
`protogeni_export` tinyint(1) NOT NULL default '0',
`taint_states` set('useronly','blackbox','dangerous') default NULL,
PRIMARY KEY (`osid`),
UNIQUE KEY `pid` (`pid`,`osname`),
KEY `OS` (`OS`),
......
......@@ -1160,6 +1160,7 @@ REPLACE INTO table_regex VALUES ('os_info','op_mode','text','regex','^[-\\w]*$',
REPLACE INTO table_regex VALUES ('os_info','nextosid','text','redirect','os_info:osid',0,0,NULL);
REPLACE INTO table_regex VALUES ('os_info','def_parentosid','text','redirect','os_info:osid',0,0,NULL);
REPLACE INTO table_regex VALUES ('os_info','reboot_waittime','int','redirect','default:int',0,2000,NULL);
REPLACE INTO table_regex VALUES ('os_info','taint_states','text','regex','^[-\\w,]*$',1,128,NULL);
REPLACE INTO table_regex VALUES ('sitevariables','name','text','regex','^[\\w\\/]+$',1,255,NULL);
REPLACE INTO table_regex VALUES ('sitevariables','value','text','redirect','default:text',0,0,NULL);
......
#
# Add noexport flag to images.
#
use strict;
use libdb;
my $impotent = 0;
sub DoUpdate($$$)
{
my ($dbhandle, $dbname, $version) = @_;
if (!DBSlotExists("os_info", "taint_states")) {
DBQueryFatal("alter table os_info add ".
" `taint_states` set('useronly','blackbox','dangerous') ".
" default NULL");
}
DBQueryFatal("REPLACE INTO table_regex VALUES ".
"('os_info','taint_states','text','regex',".
"'^[-\\\\w,]*\$',1,128,NULL)");
if (!DBSlotExists("nodes", "taint_states")) {
DBQueryFatal("alter table nodes add ".
" `taint_states` set('useronly','blackbox','dangerous') ".
" default NULL");
}
return 0;
}
# Local Variables:
# mode:perl
# End:
......@@ -252,6 +252,17 @@ sub osload ($$) {
tberror "$node: Could not map to object!";
goto failednode;
}
# Check to see if the node is tainted. If so, then the disk
# needs to be cleaned up (zeroed). If there was an explicit request
# to zero all node disks, then capture that here too.
my $zeronode = 0;
if ($nodeobject->IsTainted()) {
$zeronode = 2; # use maximum firepower.
}
elsif ($zerofree) {
$zeronode = $zerofree;
}
# Get default imageid for this node.
# NOTE that virtnodes don't have default imageids -- they are only
......@@ -300,6 +311,7 @@ sub osload ($$) {
my $defosid;
my $maxwait = 0;
my @access_keys;
my @tstates = ();
#
# Most of the DB work related to images is determining what
......@@ -449,6 +461,26 @@ sub osload ($$) {
my $osid = $rowref->{$partname};
if (defined($osid)) {
# Have the node inherit taint states from each OS
# to be loaded on it (or that is already loaded on
# it). This action is additive, i.e. the node
# will end up with the union of taint states
# across all partition OSes. We also retain any
# taint states the node had previously; it's
# important not to clear these existing states
# until OS loading and disk zeroing have been
# performed.
my $osinfo = OSinfo->Lookup($osid);
if (defined($osinfo)) {
if ($osinfo->IsTainted()) {
# Save new/incoming taint states for later...
push @tstates, $osinfo->GetTaintStates();
$nodeobject->InheritTaintStates($osinfo) == 0 or
warn "Node $node could not inherit taint ".
"states from osid $osid\n";
}
}
my %part = (
'node_id' => $node,
'partition' => $i,
......@@ -545,7 +577,7 @@ sub osload ($$) {
$reload_mode = "UISP";
$reload_func = \&SetupReloadUISP;
$reboot_required = 0; # We don't reboot motes to reload them
$zerofree = 0; # and we don't zero "the disk"
$zeronode = 0; # and we don't zero "the disk"
} else {
$reload_mode = "Frisbee";
$reload_func = \&SetupReloadFrisbee;
......@@ -589,7 +621,8 @@ sub osload ($$) {
'osid' => $defosid,
'reboot' => $reboot_required,
'wait' => $wait_required,
'zerofree' => $zerofree,
'zerofree' => $zeronode,
'tstates' => \@tstates,
'prepare' => $prepare,
'maxwait' => $maxwait,
'isremote' => $isremote,
......@@ -1010,7 +1043,21 @@ sub WaitTillReloadDone($$$$$@)
if (!$query_result->numrows) {
print STDERR "osload ($node): left reloading mode at ".`date`
if ($debug);
#
# Now reset tainting for the node. Start off
# with a clean slate. Next, apply any taint states
# previously identified and saved off for the
# still-existing and/or newly-loaded OSes. Doing this
# now allows us to clear any remnant taint states
# nullified by OS loading and/or disk zeroing.
#
$nodeobject->RemoveTaint();
my $tstates = $reload_info->{$node}{'tstates'};
if (@{$tstates}) {
$nodeobject->SetTaintStates(@{$tstates});
}
$count--;
$done{$node} = 1;
next;
......
......@@ -625,6 +625,14 @@ sub osload($$$) {
foreach my $k (keys(%{$nodeflags{$node}})) {
$nargs{$k} = $nodeflags{$node}{$k};
}
# XXX: this probably belongs in the code calling into libosload?
# Check to see if the node is tainted. If so, then the disk
# needs to be cleaned up (zeroed).
if ($nodeobject->IsTainted()) {
$nargs{'zerofree'} = 2; # use maximum firepower.
}
# Wait, don't do this -- waitmode is global; nowait is per-node!
#
# XXX hack to handle that we would see a 'nowait' flag per-node,
......@@ -1136,6 +1144,21 @@ sub WaitTillReloadDone($$$$$@)
}
else {
# success!
#
# Now reset tainting for the node. Start off
# with a clean slate. Next, apply any taint states
# previously identified and saved off for the
# still-existing and/or newly-loaded OSes. Doing this
# now allows us to clear any remnant taint states
# nullified by OS loading and/or disk zeroing.
#
$nodeobject->RemoveTaint();
my $tstates = $self->nodeinfo($nodeobject,'tstates');
if (@{$tstates}) {
$nodeobject->SetTaintStates(@{$tstates});
}
$count--;
$done{$node} = 1;
$typeobject->ReloadDone($nodeobject);
......@@ -1982,6 +2005,7 @@ sub UpdatePartitions($$)
#
my %partitions = ();
my $curmbrvers = 0;
my @tstates = ();
#
# XXX assumes a DOS MBR, but this is ingrained in the DB schema
......@@ -2077,6 +2101,26 @@ sub UpdatePartitions($$)
my $osid = $rowref->{$partname};
if (defined($osid)) {
# Have the node inherit taint states from each OS
# to be loaded on it (or that is already loaded on
# it). This action is additive, i.e. the node
# will end up with the union of all taint states
# across the OSes set for each disk partition. We
# also retain any taint states the node had
# previously; it's important not to clear these
# existing states until OS loading and disk
# zeroing have been performed.
my $osinfo = OSinfo->Lookup($osid);
if (defined($osinfo)) {
if ($osinfo->IsTainted()) {
# Save new/incoming taint states for later...
push @tstates, $osinfo->GetTaintStates();
$nodeobject->InheritTaintStates($osinfo) == 0 or
warn "Node $node_id could not inherit taint ".
"states from osid $osid\n";
}
}
my %part = (
'node_id' => $node_id,
'partition' => $i,
......@@ -2093,6 +2137,9 @@ sub UpdatePartitions($$)
}
}
# Store the taint states for this node object
$self->nodeinfo($nodeobject,'tstates',\@tstates);
#
# Now that we have processed all images, update the actual DB
# partitions table entries for this node.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment