Add a specific filter for kill commands

Use a specific KillFilter to restrict kill commands run as root.
This implementation checks the signals and the executables
actually affected, using procfs. Fixes bug 918226.

Change-Id: I6f220d741423c4b8e0e792b647760b3ef521b9b2
This commit is contained in:
Thierry Carrez 2012-01-23 14:02:23 +01:00
parent bfdb9b1f5e
commit c48fbe9843
4 changed files with 75 additions and 2 deletions

View File

@ -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"),

View File

@ -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

View File

@ -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"),

View File

@ -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", "/"]