diff --git a/nova/rootwrap/compute.py b/nova/rootwrap/compute.py index 4ced7be01571..86ed1bfce6d3 100755 --- a/nova/rootwrap/compute.py +++ b/nova/rootwrap/compute.py @@ -146,8 +146,11 @@ filterlist = [ # nova/network/linux_net.py: 'kill', '-9', pid # nova/network/linux_net.py: 'kill', '-HUP', pid + filters.KillFilter("/bin/kill", "root", + ['-9', '-HUP'], ['/usr/sbin/dnsmasq']), + # nova/network/linux_net.py: 'kill', pid - filters.CommandFilter("/bin/kill", "root"), + filters.KillFilter("/bin/kill", "root", [''], ['/usr/sbin/radvd']), # nova/network/linux_net.py: dnsmasq call filters.DnsmasqFilter("/usr/sbin/dnsmasq", "root"), diff --git a/nova/rootwrap/filters.py b/nova/rootwrap/filters.py index ab43f8f2b74d..05eaf7676665 100755 --- a/nova/rootwrap/filters.py +++ b/nova/rootwrap/filters.py @@ -88,3 +88,37 @@ class DnsmasqFilter(CommandFilter): env['FLAGFILE'] = userargs[0].split('=')[-1] env['NETWORK_ID'] = userargs[1].split('=')[-1] return env + + +class KillFilter(CommandFilter): + """Specific filter for the kill calls. + 1st argument is a list of accepted signals (emptystring means no signal) + 2nd argument is a list of accepted affected executables. + + This filter relies on /proc to accurately determine affected + executable, so it will only work on procfs-capable systems (not OSX). + """ + + def match(self, userargs): + if len(userargs) == 3: + signal = userargs.pop(1) + if signal not in self.args[0]: + # Requested signal not in accepted list + return False + else: + if len(userargs) != 2: + # Incorrect number of arguments + return False + if '' not in self.args[0]: + # No signal, but list doesn't include empty string + return False + pid = userargs[1] + try: + command = os.readlink("/proc/%d/exe" % pid) + if command not in self.args[1]: + # Affected executable not in accepted list + return False + except: + # Incorrect PID + return False + return True diff --git a/nova/rootwrap/network.py b/nova/rootwrap/network.py index 9656d52b5864..f9fd9b9c33c3 100755 --- a/nova/rootwrap/network.py +++ b/nova/rootwrap/network.py @@ -62,8 +62,11 @@ filterlist = [ # nova/network/linux_net.py: 'kill', '-9', pid # nova/network/linux_net.py: 'kill', '-HUP', pid + filters.KillFilter("/bin/kill", "root", + ['-9', '-HUP'], ['/usr/sbin/dnsmasq']), + # nova/network/linux_net.py: 'kill', pid - filters.CommandFilter("/bin/kill", "root"), + filters.KillFilter("/bin/kill", "root", [''], ['/usr/sbin/radvd']), # nova/network/linux_net.py: dnsmasq call filters.DnsmasqFilter("/usr/sbin/dnsmasq", "root"), diff --git a/nova/tests/test_nova_rootwrap.py b/nova/tests/test_nova_rootwrap.py index a38013016c5b..4dc476615905 100644 --- a/nova/tests/test_nova_rootwrap.py +++ b/nova/tests/test_nova_rootwrap.py @@ -14,6 +14,9 @@ # License for the specific language governing permissions and limitations # under the License. +import os +import subprocess + from nova.rootwrap import filters from nova.rootwrap import wrapper from nova import test @@ -60,6 +63,36 @@ class RootwrapTestCase(test.TestCase): self.assertEqual(env.get('FLAGFILE'), 'A') self.assertEqual(env.get('NETWORK_ID'), 'foobar') + @test.skip_if(not os.path.exists("/proc/%d" % os.getpid()), + "Test requires /proc filesystem (procfs)") + def test_KillFilter(self): + p = subprocess.Popen(["/bin/sleep", "5"]) + f = filters.KillFilter("/bin/kill", "root", + ["-ALRM"], + ["/bin/sleep"]) + usercmd = ['kill', '-9', p.pid] + # Incorrect signal should fail + self.assertFalse(f.match(usercmd)) + usercmd = ['kill', p.pid] + # Providing no signal should fail + self.assertFalse(f.match(usercmd)) + + f = filters.KillFilter("/bin/kill", "root", + ["-9", ""], + ["/bin/sleep"]) + usercmd = ['kill', '-9', os.getpid()] + # Our own PID does not match /bin/sleep, so it should fail + self.assertFalse(f.match(usercmd)) + usercmd = ['kill', '-9', 999999] + # Nonexistant PID should fail + self.assertFalse(f.match(usercmd)) + usercmd = ['kill', p.pid] + # Providing no signal should work + self.assertTrue(f.match(usercmd)) + usercmd = ['kill', '-9', p.pid] + # Providing -9 signal should work + self.assertTrue(f.match(usercmd)) + def test_skips(self): # Check that all filters are skipped and that the last matches usercmd = ["cat", "/"]