| # Copyright 2021 The ChromiumOS Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Python API for the prctl() syscall.""" |
| |
| import ctypes |
| import enum |
| import errno |
| import os |
| from typing import List, Optional, Union |
| |
| from chromite.utils import libc |
| |
| |
| PrctlType = Union["ctypes._CArgObject", int] |
| |
| |
| class Option(enum.IntEnum): |
| """Known prctl options.""" |
| |
| # arg2 is int* input. |
| SET_PDEATHSIG = 1 |
| |
| # arg2 is int* output. |
| GET_PDEATHSIG = 2 |
| |
| # arg2 is char* input. |
| SET_NAME = 15 |
| |
| # arg2 is char* output. |
| GET_NAME = 16 |
| |
| # arg2 is int input. |
| SET_NO_NEW_PRIVS = 38 |
| |
| # arg2 is int* output. |
| GET_NO_NEW_PRIVS = 39 |
| |
| |
| class Error(Exception): |
| """Base class for errors in this module.""" |
| |
| |
| class PrctlError(OSError, Error): |
| """prctl() call failed.""" |
| |
| option: Option |
| returncode: int |
| # NB: Can't call |args| as Error.args already takes that over. |
| prargs: List[Union[PrctlType, str]] |
| |
| # pylint: disable=redefined-outer-name |
| def __init__( |
| self, |
| option: Option, |
| returncode: int, |
| prargs: Optional[List[Union[PrctlType, str]]] = None, |
| errno: Optional[int] = None, |
| ) -> None: |
| if errno is None: |
| errno = ctypes.get_errno() |
| # We have to expand the errno string ourselves as Python will not, and |
| # will ignore the first argument (errno) if not provided. |
| OSError.__init__(self, errno, os.strerror(errno)) |
| Error.__init__(self, option, returncode, prargs, errno) |
| |
| self.option = option |
| self.returncode = returncode |
| self.prargs = list(prargs) if prargs else [] |
| |
| @property |
| def msg(self) -> str: |
| str_args = "".join(", {x}" for x in self.prargs) |
| str_errno = "" |
| if bool(self.errno): |
| str_errno = ( |
| f" ({errno.errorcode.get(self.errno, 'E???')}[{self.errno}])" |
| ) |
| return ( |
| f"prctl({self.option.name}{str_args}) failed: " |
| f"ret={self.returncode}{str_errno}" |
| ) |
| |
| def __str__(self) -> str: |
| str_errno = "" |
| if bool(self.errno): |
| str_errno = f", {errno.errorcode.get(self.errno, 'E???')}" |
| return ( |
| f"PrctlError({self.option.name}, {self.returncode}, " |
| f"{self.prargs}{str_errno})" |
| ) |
| |
| |
| def prctl( |
| option: Option, |
| arg2: PrctlType = 0, |
| arg3: PrctlType = 0, |
| arg4: PrctlType = 0, |
| arg5: PrctlType = 0, |
| ) -> int: |
| """Wrapper around prctl(). |
| |
| See the man page for documentation: |
| https://man7.org/linux/man-pages/man2/prctl.2.html |
| |
| Examples: |
| # For options that only take input integers, the API is simple. |
| prctl.prctl(prctl.Option.SET_PDEATHSIG, signal.SIGHUP) |
| |
| # For options that output arguments, the caller must pass in pointers. |
| arg2 = ctypes.c_int(0) |
| prctl.prctl(prctl.Option.GET_PDEATHSIG, ctypes.byref(arg2)) |
| print(arg2.value) |
| """ |
| clib = libc.GetLibc() |
| |
| # Clear the errno so the caller can determine whether this call failed. |
| ctypes.set_errno(0) |
| |
| # NB: It's safe to call prctl with unused args as they'll get ignored, and |
| # it's safer to explicitly specify a default of 0 rather than leave whatever |
| # garbage is in the register. |
| ret = clib.prctl(option, arg2, arg3, arg4, arg5) |
| |
| if not isinstance(ret, int): |
| raise TypeError( |
| f"Expected an int return value from libc.prctl, got {ret!r}" |
| ) |
| |
| c_errno = ctypes.get_errno() |
| if c_errno: |
| raise PrctlError(option, ret, [arg2, arg3, arg4, arg5], c_errno) |
| |
| return ret |
| |
| |
| def _set_int(option: Option, value: int) -> None: |
| """Helper for functions that have a single input integer.""" |
| ret = prctl(option, value) |
| if ret: |
| raise PrctlError(option, ret, [value]) |
| |
| |
| def _get_int(option: Option) -> int: |
| """Helper for functions that have a single output integer.""" |
| value = ctypes.c_int(0) |
| ret = prctl(option, ctypes.byref(value)) |
| if ret: |
| raise PrctlError(option, ret) |
| return value.value |
| |
| |
| def _set_str(option: Option, value: str) -> None: |
| """Helper for functions that have a single input string.""" |
| c_str = ctypes.create_string_buffer(value.encode("utf-8")) |
| ret = prctl(option, ctypes.byref(c_str)) |
| if ret: |
| raise PrctlError(option, ret, [value]) |
| |
| |
| def _get_str(option: Option, length: int) -> str: |
| """Helper for functions that have a single output string.""" |
| c_str = ctypes.create_string_buffer(length) |
| ret = prctl(Option.GET_NAME, ctypes.byref(c_str)) |
| if ret: |
| raise PrctlError(option, ret) |
| return c_str.value.decode("utf-8") |
| |
| |
| def set_pdeathsig(value: int) -> None: |
| """SET_PDEATHSIG wrapper.""" |
| _set_int(Option.SET_PDEATHSIG, value) |
| |
| |
| def get_pdeathsig() -> int: |
| """GET_PDEATHSIG wrapper.""" |
| return _get_int(Option.GET_PDEATHSIG) |
| |
| |
| def set_name(name: str) -> None: |
| """SET_NAME (thread name) wrapper.""" |
| _set_str(Option.SET_NAME, name) |
| |
| |
| def get_name() -> str: |
| """GET_NAME (thread name) wrapper.""" |
| # Return is 16 bytes, and it's always NUL terminated. |
| return _get_str(Option.GET_NAME, 16) |
| |
| |
| def set_no_new_privs(value: int = 1) -> None: |
| """SET_NO_NEW_PRIVS wrapper.""" |
| _set_int(Option.SET_NO_NEW_PRIVS, value) |
| |
| |
| def get_no_new_privs() -> int: |
| """GET_NO_NEW_PRIVS wrapper.""" |
| ret = prctl(Option.GET_NO_NEW_PRIVS) |
| if ret not in (0, 1): |
| raise PrctlError(Option.GET_NO_NEW_PRIVS, ret) |
| return ret |