diff --git a/db/EmulabConstants.pm.in b/db/EmulabConstants.pm.in
index f72ca7862a23fa8507a2357036bdd2fb5a1d5225..d2b99912b967cca4b1a31070ec04540a29730042 100644
--- a/db/EmulabConstants.pm.in
+++ b/db/EmulabConstants.pm.in
@@ -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:
diff --git a/db/Node.pm.in b/db/Node.pm.in
index ae728106240cf5cf0a7c4ceaa4b12d487c429c13..b1d9053f70233d3d5db21a48da8b856c8668a6ab 100755
--- a/db/Node.pm.in
+++ b/db/Node.pm.in
@@ -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;
diff --git a/db/OSinfo.pm.in b/db/OSinfo.pm.in
index 35377f35d96975d945c7ee93d12f56511244f1b7..4128dd610ec2b37a278ffbc173845b2a33c0c2fc 100644
--- a/db/OSinfo.pm.in
+++ b/db/OSinfo.pm.in
@@ -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;
diff --git a/sql/database-create.sql b/sql/database-create.sql
index 863acb84fe955c115e595880d988ba0d223cd7d5..9e21a1b7ae0af4192241658b07e7a025255c430d 100644
--- a/sql/database-create.sql
+++ b/sql/database-create.sql
@@ -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`),
diff --git a/sql/database-fill.sql b/sql/database-fill.sql
index e67552a3eb8be9d89d18e8640483f98706381d82..0c682e3602ffc4969f1fbc9469a4a4757c195ac1 100644
--- a/sql/database-fill.sql
+++ b/sql/database-fill.sql
@@ -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);
diff --git a/sql/updates/4/385 b/sql/updates/4/385
new file mode 100644
index 0000000000000000000000000000000000000000..19eaae3f78a03b3cb9b49e211676936c171ba1c6
--- /dev/null
+++ b/sql/updates/4/385
@@ -0,0 +1,33 @@
+#
+# 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:
diff --git a/tbsetup/libosload.pm.in b/tbsetup/libosload.pm.in
index e16fbb77a915798e4ab06cd6c8fc59b5f3cb6e88..c83eb57c3f49d1d390ac18c5e424e9ba31a3e07b 100644
--- a/tbsetup/libosload.pm.in
+++ b/tbsetup/libosload.pm.in
@@ -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;
diff --git a/tbsetup/libosload_new.pm.in b/tbsetup/libosload_new.pm.in
index a937e696ebf38c2d33c3ec5af56c0a9a8bd4d493..4d2036f99c31be75a9b7c5cfdd57431a8f147a72 100644
--- a/tbsetup/libosload_new.pm.in
+++ b/tbsetup/libosload_new.pm.in
@@ -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.