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:
parent
6775cdf26a
commit
e5f9a393ce
@ -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):
|
||||
|
@ -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'
|
||||
|
Loading…
Reference in New Issue
Block a user