From f3fd72a2cdf84b3f425aa2a0641cf596a89bb96f Mon Sep 17 00:00:00 2001 From: Michelle McDaniel Date: Thu, 23 Jul 2026 13:13:54 -0700 Subject: [PATCH 1/3] Validate that process is actually a patching operation before terminating There are times when the previous patching operation will not be running (reboot, etc), where its PID will be reused before the next patching operation starts. When ProcessHandler checks for running previous patching operations, it will identify this new process which is not the patching extension as a process that needs to be terminated. If this process is a required process for the service, it causes that service to effectively crash. This change checks the process's cmdline by reading the pid's cmdline file in the proc filesystem. If it contains the patching extension Core filename, then we can be more confident that the process we are about to end is the correct one. If it does not find the core filename in the cmdline, we can guarantee that the process is not a patching operation, and we will not end that process. --- src/extension/src/ProcessHandler.py | 16 +++++++++++++++- src/extension/tests/Test_ProcessHandler.py | 6 ++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/extension/src/ProcessHandler.py b/src/extension/src/ProcessHandler.py index b3ea00caf..f051d1650 100644 --- a/src/extension/src/ProcessHandler.py +++ b/src/extension/src/ProcessHandler.py @@ -199,9 +199,23 @@ def is_process_running(self, pid): # According to "man 2 kill" possible error values are (EINVAL, EPERM, ESRCH) Thus considering this as an error return False + def is_process_patching_operation(self, pid): + try: + with self.env_layer.file_system.open("/proc/{0}/cmdline".format(str(pid)), mode="r") as cmdline_file: + cmdline = cmdline_file.read() + if Constants.CORE_CODE_FILE_NAME in cmdline: + self.logger.log_debug("Process is a patching operation. [PID={0}]".format(str(pid))) + return True + else: + self.logger.log_debug("Process is not a patching operation. [PID={0}]".format(str(pid))) + return False + except Exception as error: + self.logger.log_debug("Error checking if process is a patching operation. [PID={0}] [Error={1}]".format(str(pid), repr(error))) + return True # If we cannot determine, assume it is a patching operation to be safe + def kill_process(self, pid): try: - if self.is_process_running(pid): + if self.is_process_running(pid) and self.is_process_patching_operation(pid): self.logger.log("Terminating process: [PID={0}]".format(str(pid))) os.kill(pid, signal.SIGTERM) except OSError as error: diff --git a/src/extension/tests/Test_ProcessHandler.py b/src/extension/tests/Test_ProcessHandler.py index 99e192508..2e75558c7 100644 --- a/src/extension/tests/Test_ProcessHandler.py +++ b/src/extension/tests/Test_ProcessHandler.py @@ -49,6 +49,9 @@ def tearDown(self): def mock_is_process_running_to_return_true(self, pid): return True + def mock_is_process_running_to_return_true(self, pid): + return True + def mock_os_kill_to_raise_exception(self, pid, sig): raise OSError @@ -133,6 +136,8 @@ def test_kill_process(self): # setting mocks is_process_running_backup = ProcessHandler.is_process_running ProcessHandler.is_process_running = self.mock_is_process_running_to_return_true + is_process_patching_operation_backup = ProcessHandler.is_process_patching_operation + ProcessHandler.is_process_patching_operation = self.mock_is_process_patching_operation_to_return_true os_kill_backup = os.kill os.kill = self.mock_os_kill_to_raise_exception @@ -143,6 +148,7 @@ def test_kill_process(self): # reseting mocks ProcessHandler.is_process_running = is_process_running_backup + ProcessHandler.is_process_patching_operation = is_process_patching_operation_backup os.kill = os_kill_backup def test_get_python_cmd(self): From 7ca6ffdaabf02a00fb31039d47d9b45f6792b5c1 Mon Sep 17 00:00:00 2001 From: Michelle McDaniel Date: Thu, 23 Jul 2026 13:55:55 -0700 Subject: [PATCH 2/3] fix test --- src/extension/tests/Test_ProcessHandler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/extension/tests/Test_ProcessHandler.py b/src/extension/tests/Test_ProcessHandler.py index 2e75558c7..b13ad8704 100644 --- a/src/extension/tests/Test_ProcessHandler.py +++ b/src/extension/tests/Test_ProcessHandler.py @@ -49,7 +49,7 @@ def tearDown(self): def mock_is_process_running_to_return_true(self, pid): return True - def mock_is_process_running_to_return_true(self, pid): + def mock_is_process_patching_operation_to_return_true(self, pid): return True def mock_os_kill_to_raise_exception(self, pid, sig): From 0761c2fc7f277462cf427f1059fd5d863c29077b Mon Sep 17 00:00:00 2001 From: Michelle McDaniel Date: Fri, 24 Jul 2026 09:20:34 -0700 Subject: [PATCH 3/3] Address feedback --- src/extension/src/ProcessHandler.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/extension/src/ProcessHandler.py b/src/extension/src/ProcessHandler.py index f051d1650..71030572f 100644 --- a/src/extension/src/ProcessHandler.py +++ b/src/extension/src/ProcessHandler.py @@ -201,13 +201,17 @@ def is_process_running(self, pid): def is_process_patching_operation(self, pid): try: - with self.env_layer.file_system.open("/proc/{0}/cmdline".format(str(pid)), mode="r") as cmdline_file: + # Patching operation cmdline will look like: + # /usr/bin/python3.10 /var/lib/waagent/Microsoft.CPlat.Core.LinuxPatchExtension-1.6.69/MsftLinuxPatchCore.py + with self.env_layer.file_system.open("/proc/{0}/cmdline".format(str(pid)), mode="rb") as cmdline_file: cmdline = cmdline_file.read() + cmdline = cmdline.replace(b'\x00', b' ').decode("utf-8") # cmdline is null-separated, so replacing nulls with spaces for easier parsing and logging + if Constants.CORE_CODE_FILE_NAME in cmdline: - self.logger.log_debug("Process is a patching operation. [PID={0}]".format(str(pid))) + self.logger.log_verbose("Process is a patching operation. [PID={0}]".format(str(pid))) return True else: - self.logger.log_debug("Process is not a patching operation. [PID={0}]".format(str(pid))) + self.logger.log_debug("Process is not a patching operation. [PID={0}] [CmdLine={1}]".format(str(pid), cmdline)) return False except Exception as error: self.logger.log_debug("Error checking if process is a patching operation. [PID={0}] [Error={1}]".format(str(pid), repr(error)))