Handle renamed executables with KillFilter

A running process may refer to a renamed executable
referenced by /proc/$pid/exe, which happens with
updated executables on RPM based systems.
In that case we defer to the path in /proc/$pid/cmdline
if it exists.

Change-Id: I113f2f8ebd56f3b05c420310c4b1e84ba6f17fcf
Closes-Bug: 1482316
This commit is contained in:
Pádraig Brady 2015-08-07 17:12:25 +01:00
parent 6775cdf26a
commit e5f9a393ce
2 changed files with 81 additions and 19 deletions

View File

@ -156,6 +156,54 @@ class KillFilter(CommandFilter):
def __init__(self, *args):
super(KillFilter, self).__init__("/bin/kill", *args)
def _program_path(self, command):
"""Determine the full path for command"""
if os.path.isabs(command):
return command
else:
for path in os.environ.get('PATH', '').split(os.pathsep):
program = os.path.join(path, command)
if os.path.isfile(program):
return program
return command
def _program(self, pid):
"""Determine the program associated with pid"""
try:
command = os.readlink("/proc/%d/exe" % int(pid))
except (ValueError, EnvironmentError):
# Incorrect PID
return None
# NOTE(yufang521247): /proc/PID/exe may have '\0' on the
# end, because python doesn't stop at '\0' when read the
# target path.
command = command.partition('\0')[0]
# NOTE(dprince): /proc/PID/exe may have ' (deleted)' on
# the end if an executable is updated or deleted
if command.endswith(" (deleted)"):
command = command[:-len(" (deleted)")]
# /proc/PID/exe may have been renamed with
# a ';......' or '.#prelink#......' suffix etc.
# So defer to /proc/PID/cmdline in that case.
if not os.path.isfile(command):
try:
with open("/proc/%d/cmdline" % int(pid)) as pfile:
cmdline = pfile.read().partition('\0')[0]
cmdline = self._program_path(cmdline)
if os.path.isfile(cmdline):
command = cmdline
# Note we don't return None if cmdline doesn't exist
# as that will allow killing a process where the exe
# has been removed from the system rather than updated.
except EnvironmentError:
return None
return command
def match(self, userargs):
if not userargs or userargs[0] != "kill":
return False
@ -173,22 +221,11 @@ class KillFilter(CommandFilter):
if len(self.args) > 1:
# No signal requested, but filter requires specific signal
return False
try:
command = os.readlink("/proc/%d/exe" % int(args[1]))
except (ValueError, OSError):
# Incorrect PID
command = self._program(args[1])
if not command:
return False
# NOTE(yufang521247): /proc/PID/exe may have '\0' on the
# end, because python doesn't stop at '\0' when read the
# target path.
command = command.partition('\0')[0]
# NOTE(dprince): /proc/PID/exe may have ' (deleted)' on
# the end if an executable is updated or deleted
if command.endswith(" (deleted)"):
command = command[:-len(" (deleted)")]
kill_command = self.args[0]
if os.path.isabs(kill_command):

View File

@ -226,20 +226,45 @@ class RootwrapTestCase(testtools.TestCase):
def test_KillFilter_deleted_exe(self):
"""Makes sure deleted exe's are killed correctly."""
f = filters.KillFilter("root", "/bin/commandddddd")
command = "/bin/commandddddd"
f = filters.KillFilter("root", command)
usercmd = ['kill', 1234]
# Providing no signal should work
with mock.patch('os.readlink') as readlink:
readlink.return_value = '/bin/commandddddd (deleted)'
self.assertTrue(f.match(usercmd))
readlink.return_value = command + ' (deleted)'
with mock.patch('os.path.isfile') as exists:
def fake_exists(path):
return path == command
exists.side_effect = fake_exists
self.assertTrue(f.match(usercmd))
def test_KillFilter_upgraded_exe(self):
"""Makes sure upgraded exe's are killed correctly."""
f = filters.KillFilter("root", "/bin/commandddddd")
command = "/bin/commandddddd"
usercmd = ['kill', 1234]
with mock.patch('os.readlink') as readlink:
readlink.return_value = '/bin/commandddddd\0\05190bfb2 (deleted)'
self.assertTrue(f.match(usercmd))
readlink.return_value = command + '\0\05190bfb2 (deleted)'
with mock.patch('os.path.isfile') as exists:
def fake_exists(path):
return path == command
exists.side_effect = fake_exists
self.assertTrue(f.match(usercmd))
def test_KillFilter_renamed_exe(self):
"""Makes sure renamed exe's are killed correctly."""
command = "/bin/commandddddd"
f = filters.KillFilter("root", command)
usercmd = ['kill', 1234]
with mock.patch('os.readlink') as readlink:
readlink.return_value = command + ';90bfb2 (deleted)'
m = mock.mock_open(read_data=command)
with mock.patch("__builtin__.open", m, create=True):
with mock.patch('os.path.isfile') as exists:
def fake_exists(path):
return path == command
exists.side_effect = fake_exists
self.assertTrue(f.match(usercmd))
def test_ReadFileFilter(self):
goodfn = '/good/file.name'