Welcome to pyprctl’s documentation!
pyprctl
provides an interface to Linux’s prctl()
system call, and to Linux capabilities in general, that is written in pure Python with no external dependencies. (You don’t even need to have libcap
installed!)
This library and portions of its API were inspired by python-prctl
, but it is not a fork.
Important security note!
On Linux, credentials (real/effective/saved UIDs, real/effective/saved GIDs, supplementary groups, and all of the capability sets) are per-thread attributes. If you change these attributes in one thread, other threads in the same process are unaffected. This can lead to security issues.
To try to mitigate this, glibc and musl make clever use of realtime signals to synchronize changes made to the real/effective/saved UIDs/GIDs across all threads. libcap offers “libpsx”, which enables similar synchronization of capability changes.
pyprctl
, however, makes no attempt to synchronize any changes it makes to a thread’s UIDs/GIDs/capabilities. In fact, when changing UIDs/GIDs it deliberately works around glibc/musl’s UID/GID synchronization. If you use pyprctl
’s capability manipulation functions in multithreaded programs, you are responsible for synchronizing any changes across threads (if this is necessary to properly secure your application).
(Note: python-prctl
doesn’t link against libpsx, so it behaves largely the same way! It just doesn’t document this.)
Platform support
Architecture support
pyprctl
should work on at least the following architectures (both 32-bit and 64-bit versions): x86, ARM, RISC-V, SPARC, and PowerPC. However, it has only been tested on x86_64 and 32-bit ARM.
musl libc
pyprctl
should work on systems using musl libc. It’s been tested on Alpine Linux and on the musl-based distribution of Void Linux.
Statically linked systems
pyprctl
might work on statically linked systems, but this has not been tested.
API documentation
- class pyprctl.Cap(value)
Bases:
enum.Enum
An enum representing the different Linux capabilities.
See capabilities(7) for more information on each capability.
- AUDIT_CONTROL = 30
- AUDIT_READ = 37
- AUDIT_WRITE = 29
- BLOCK_SUSPEND = 36
- BPF = 39
- CHECKPOINT_RESTORE = 40
- CHOWN = 0
- DAC_OVERRIDE = 1
- DAC_READ_SEARCH = 2
- FOWNER = 3
- FSETID = 4
- IPC_LOCK = 14
- IPC_OWNER = 15
- KILL = 5
- LEASE = 28
- LINUX_IMMUTABLE = 9
- MAC_ADMIN = 33
- MAC_OVERRIDE = 32
- MKNOD = 27
- NET_ADMIN = 12
- NET_BIND_SERVICE = 10
- NET_BROADCAST = 11
- NET_RAW = 13
- PERFMON = 38
- SETFCAP = 31
- SETGID = 6
- SETPCAP = 8
- SETUID = 7
- SYSLOG = 34
- SYS_ADMIN = 21
- SYS_BOOT = 22
- SYS_CHROOT = 18
- SYS_MODULE = 16
- SYS_NICE = 23
- SYS_PACCT = 20
- SYS_PTRACE = 19
- SYS_RAWIO = 17
- SYS_RESOURCE = 24
- SYS_TIME = 25
- SYS_TTY_CONFIG = 26
- WAKE_ALARM = 35
- classmethod from_name(name: str) pyprctl.caps.Cap
Look up a capability by name.
Roughly equivalent to
cap_from_name()
in libcap. Names are matched case-insensitively, but they must include acap_
prefix (also case-insensitive;CAP_
andCap_
are valid too).
- is_supported() bool
Returns whether the running kernel supports this capability.
- classmethod probe_supported() Set[pyprctl.caps.Cap]
Returns the set of capabilities supported by the running kernel.
- class pyprctl.CapState(effective: Set[pyprctl.caps.Cap] = <factory>, permitted: Set[pyprctl.caps.Cap] = <factory>, inheritable: Set[pyprctl.caps.Cap] = <factory>)
Bases:
object
Represents a thread’s capability state (i.e. the effective, permitted and inheritable capability sets).
- copy() pyprctl.caps.CapState
- effective: Set[pyprctl.caps.Cap]
The effective capability set (used for permission checks)
- classmethod from_text(text: str) pyprctl.caps.CapState
Reconstruct a capability state from a textual representation. For example:
=
,=p
, orcap_chown=ep
.
- classmethod get_current() pyprctl.caps.CapState
Get the capability state of the current thread.
- classmethod get_for_pid(pid: int) pyprctl.caps.CapState
Get the capability state of the process (or thread) with the given PID (or TID).
If
pid
is 0, this is equivalent toCapState.get_current()
.
- inheritable: Set[pyprctl.caps.Cap]
The inheritable capabilities set. This is preserved across
exec()
. In addition, whenexec()
-ing a program that has the corresponding capabilities in its inheritable set, these capabilities will be added to the permitted set. See capabilities(7) for more details.
- permitted: Set[pyprctl.caps.Cap]
The permitted capability set. This is the bounding set for the thread’s effective capabilities. It also limits which capabilities a thread that does not have CAP_SETPCAP can add to its inheritable set. See capabilities(7) for more details.
- set_current() None
Set the capability state of the current thread to the capability set represented by this object.
Note: If the capability sets stored in this object contain capabilities that the running kernel does not support, the kernel will silently ignore them!
- class pyprctl.FileCaps(effective: bool, permitted: Set[pyprctl.caps.Cap], inheritable: Set[pyprctl.caps.Cap], rootid: Optional[int] = None)
Bases:
object
Represents the capability sets attached to an executable file.
- copy() pyprctl.file_caps.FileCaps
- effective: bool
If this is
True
, it indicates a “capability-dumb” binary. When this program is executed, all capabilities in the new process’s permitted set will also be copied to the process’s effective set.In addition, if a binary has this bit set, the kernel will refuse to launch it if the new process would not obtain the full set of capabilities specified in the permitted set. See capabilities(7) for more details.
- classmethod from_text(text: str) pyprctl.file_caps.FileCaps
Reconstruct a set of file capabilities from a textual representation. For example:
=
,=p
, orcap_chown=ep
.Note that this method will raise an error if the specified “effective” set is not empty and is also different from the “permitted” set. This is because Linux file capabilities only have a single bit for specifying the “effective” permissions, which indicates whether or not the permitted set should be copied to the effective set.
- classmethod get_for_file(path: Union[int, str, bytes, os.PathLike[str], os.PathLike[bytes]]) FileCaps
Retrieves the file capabilities attached to the given file.
path
is as foros.getxattr()
.
- inheritable: Set[pyprctl.caps.Cap]
The inheritable set. This set is ANDed with the inheritable set of the launching program and the resulting capabilities are added to the thread’s permitted set.
- permitted: Set[pyprctl.caps.Cap]
The permitted set; automatically added to the thread’s permitted set.
- classmethod remove_for_file(path: Union[int, str, bytes, os.PathLike[str], os.PathLike[bytes]]) None
Removes the file capabilities attached to the given file.
path
is as foros.removexattr()
.
- rootid: Optional[int] = None
For version 3 capability sets, this represents the root user ID of the user namespace in which the file capability extended attribute was created. See capabilities(7) for more details.
- set_for_file(path: Union[int, str, bytes, os.PathLike[str], os.PathLike[bytes]]) None
Sets the file capabilities attached to the given file to the state represented by this object.
path
is as foros.setxattr()
.
- class pyprctl.MCEKillPolicy(value)
Bases:
enum.Enum
An enum representing the possible machine check memory corruption kill policies, for use with
get_mce_kill()
andset_mce_kill()
.Essentially, this determines when a thread will be sent a SIGBUS signal if memory corruption is detected. See
PR_MCE_KILL
in prctl(2) and/proc/sys/vm/memory_failure_early_kill
in proc(5) for more information.- DEFAULT = 2
When irrecoverable corruption is detected, follow the system-wide default action as specified by the contents of
/proc/sys/vm/memory_failure_early_kill
(see proc(5) for more information).
- EARLY = 1
“Early kill” -> As soon as irrecoverable corruption is detected, kill this thread if it has the corrupted page mapped.
- LATE = 0
“Late kill” -> If irrecoverable corruption is detected, only kill this thread if it tries to access the corrupted page.
- class pyprctl.Secbits(value)
Bases:
enum.Flag
Represents the different securebits that can be used to change the kernel’s handling of capabilities for UID 0.
- KEEP_CAPS = 16
Provides the same functionality as
get_keepcaps()
andset_keepcaps()
.Note that changes made with
get_keepcaps()
/set_keepcaps()
are reflected in the value of this flag as returned byget_securebits()
, and vice versa. Since changing the securebits requires CAP_SETPCAP, it may be better to use those functions instead if this is the only securebit that you need to change.
- KEEP_CAPS_LOCKED = 32
“Locks” the KEEP_CAPS securebit so it cannot be changed.
Note: If the KEEP_CAPS securebit is set, even if it is “locked” using this flag, the kernel will still clear it on an
exec()
. So this setting is only really useful to lock the KEEP_CAPS securebit as “off”.
- NOROOT = 1
If this bit is set, the kernel will not grant capabilities to set-user-ID-root programs, or to processes with an effective or real user ID of 0 on
exec()
. See capabilities(7) for more details.
- NOROOT_LOCKED = 2
“Locks” the NOROOT securebit so it cannot be changed.
- NO_CAP_AMBIENT_RAISE = 64
Disables raising ambient capabilities (such as with
cap_ambient_raise()
).
- NO_CAP_AMBIENT_RAISE_LOCKED = 128
“Locks” the NO_CAP_AMBIENT_RAISE securebit so it cannot be changed.
- NO_SETUID_FIXUP = 4
Stops the kernel from adjusting the process’s permitted/effective/ambient capabilities when the process’s effective and filesystem UIDs are switched between 0 and nonzero. See capabilities(7) for more details.
- NO_SETUID_FIXUP_LOCKED = 8
“Locks” the NO_SETUID_FIXUP securebit so it cannot be changed.
- class pyprctl.TimingMethod(value)
Bases:
enum.Enum
An enum representing the possible process timing methods, for use with
get_timing()
andset_timing()
.See
PR_SET_TIMING
in prctl(2) for more details.- STATISTICAL = 0
The traditional statistical process timing method.
- TIMESTAMP = 1
Accurate timestamp-based process timing (currently unimplemented; trying to select it will raise an error).
- pyprctl._sys_exit(res: int) None
Call the
_exit()
system call. This exits the calling thread, but does not terminate other threads in the same process.
- pyprctl.cap_ambient_clear_all() None
Clear all ambient capabilities from the current thread.
- pyprctl.cap_ambient_is_set(cap: pyprctl.caps.Cap) Optional[bool]
Check whether the given capability is raised in the current thread’s ambient capability set.
This returns
True
if the capability is raised,False
if it is lowered, andNone
if the kernel does not support this capability (may arise when using newer capabilities on older kernels).
- pyprctl.cap_ambient_lower(cap: pyprctl.caps.Cap) None
Lower the given capability in the current thread’s ambient set.
This function will fail with
EINVAL
if the kernel does not support the specified capability.
- pyprctl.cap_ambient_probe() Set[pyprctl.caps.Cap]
“Probe” the current thread’s ambient capability set and return a set of all the capabilities that are raised in this thread’s ambient capability set.
- pyprctl.cap_ambient_raise(cap: pyprctl.caps.Cap) None
Raise the given capability in the current thread’s ambient set.
(The capability must already be present in the thread’s permitted set and and the thread’s inheritable set, and the SECBIT_NO_CAP_AMBIENT_RAISE securebit must not be set.)
This function will fail with
EINVAL
if the kernel does not support the specified capability.
- pyprctl.cap_ambient_supported() bool
Check whether the running kernel supports ambient capabilities.
- pyprctl.cap_set_ids(*, uid: Optional[int] = None, gid: Optional[int] = None, groups: Optional[Iterable[int]] = None, preserve_effective_caps: bool = False) None
Set UID/GID/supplementary groups while preserving permitted capabilities.
This combines the functionality of
libcap
’scap_setuid()
andcap_setgroups()
, while also providing greater flexibility.Note: This function only operates on the current thread, not the process as a whole. This is because of the way Linux operates. If you call this function from a multithreaded program, you are responsible for synchronizing changes across threads to ensure proper security.
This function performs the following actions in order. (Note: If
gid
is notNone
orgroups
is notNone
, CAP_SETGID will first be raised in the thread’s effective set, and ifuid
is notNone
then CAP_SETUID will be raised.)If
gid
is notNone
, the thread’s real, effective and saved GIDs will be set togid
.If
groups
is notNone
, the thread’s supplementary group list will be set togroups
.If
uid
is notNone
, the thread’s real, effective and saved UIDs will be set touid
.If
preserve_effective_caps
isTrue
, after this is done, the effective capability set will be restored to its original contents. By default, this function mimicslibcap
and empties the effective capability set before returning.
Note: If this function fails and raises an
OSError
, the thread’s UIDs, GIDs, supplementary groups, and capability sets are in an unknown and possibly inconsistent state. This is EXTREMELY DANGEROUS! If you are unable to revert the changes, abort as soon as possible.
- pyprctl.capbset_drop(cap: pyprctl.caps.Cap) None
Remove the given capability from the current thread’s bounding capability set.
(This requires the CAP_SETPCAP capability.)
This function will fail with
EINVAL
if the kernel does not support the specified capability.
- pyprctl.capbset_probe() Set[pyprctl.caps.Cap]
“Probe” the current thread’s bounding capability set and return a set of all the capabilities that are present in this thread’s bounding capability set.
- pyprctl.capbset_read(cap: pyprctl.caps.Cap) Optional[bool]
Check whether the given capability is present in the current thread’s bounding capability set.
This returns
True
if the capability is present,False
if it is not, andNone
if the kernel does not support this capability (may arise when using newer capabilities on older kernels).
- pyprctl.get_child_subreaper() bool
Get the “child subreaper” attribute of the current process.
See
set_child_subreaper()
.
- pyprctl.get_dumpable() bool
Get whether the “dumpable” attribute is set on the current process.
See
set_dumpable()
.
- pyprctl.get_keepcaps() bool
Get the “keep capabilities” flag on the current process.
See
set_keepcaps()
.
- pyprctl.get_mce_kill() pyprctl.misc.MCEKillPolicy
Get the machine check memory corruption kill policy for the current thread. See
set_mce_kill()
.
- pyprctl.get_name() str
Get the name of the current thread as a string.
- pyprctl.get_no_new_privs() bool
Get whether the no-new-privileges flag is set on the current thread.
See
set_no_new_privs()
.
- pyprctl.get_pdeathsig() Optional[Union[signal.Signals, int]]
Get the parent-death signal of the current process (see
set_pdeathsig()
for details).If the parent-death signal is cleared, this function returns
None
. Otherwise, it returns asignal.Signals
object (or anint
if the value is not insignal.Signals
).
- pyprctl.get_securebits() pyprctl.caps.Secbits
Get the current secure bits.
- pyprctl.get_timerslack() int
Get the current thread’s timer slack value.
See
set_timerslack()
.
- pyprctl.get_timing() pyprctl.misc.TimingMethod
Get the current process timing mode.
- pyprctl.getfsgid() int
Get the current thread’s filesystem GID (see setfsgid(2) for details).
This calls the
setfsgid()
syscall with the argument -1 (which will make it always fail) and returns the result.
- pyprctl.getfsuid() int
Get the current thread’s filesystem UID (see setfsuid(2) for details).
This calls the
setfsuid()
syscall with the argument -1 (which will make it always fail) and returns the result.
- pyprctl.scoped_effective_caps(effective: Iterable[pyprctl.caps.Cap]) Iterator[None]
When used as a context manager, this function sets the effective capability set to contain only the specified capabilities, then restores it to its original contents after the body of the context manager has executed.
For example:
with scoped_effective_caps([Cap.CHOWN]): ... # CAP_CHOWN is raised in the effective set; all other capabilites are lowered
Note
Changes made to the effective capability set in the body of the context manager will not be preserved. This function will still revert the effective capability set to its original contents when the body of the context manager finishes executing.
However, changes made to any of the other 4 capability sets (permitted, inheritable, ambient, and bounding) in the body of the context manager will be preserved. (Be careful not to remove any of the capabilities present in the original effective set from the permitted set, or this function may fail to revert to the original effective set.)
- pyprctl.set_child_subreaper(flag: bool) None
Set the “child subreaper” attribute on the current process.
When a process dies, its children are reparented to the nearest ancestor with the “child subreaper” attribute set.
- pyprctl.set_dumpable(flag: bool) None
Set the “dumpable” attribute on the current process.
This controls whether a core dump will be produced if the process receives a signal whose default behavior is to produce a core dump.
In addition, processes that are not dumpable cannot be attached with
ptrace()
PTRACE_ATTACH
.
- pyprctl.set_keepcaps(flag: bool) None
Set the “keep capabilities” flag on the current thread.
This flag, which is always cleared across an
exec()
, allows a thread to preserve its permitted capability set when switching all of its UIDs to nonzero values. See capabilities(7) for more information.
- pyprctl.set_mce_kill(policy: pyprctl.misc.MCEKillPolicy) None
Set the machine check memory corruption kill policy for the current thread.
See
MCEKillPolicy
for more details.
- pyprctl.set_name(name: Union[str, bytes]) None
Set the name of the current thread.
The name is silently truncated to the first 16 bytes. This includes the trailing NUL, so only the first 15 characters of the given
name
will be used.
- pyprctl.set_no_new_privs() None
Set the no-new-privileges flag on the current thread.
Once this flag is set, it cannot be unset. This flag guarantees that in this thread and in all of its children, no
exec()
call can ever result in elevated privileges. See prctl(2) for more information.
- pyprctl.set_pdeathsig(sig: Optional[Union[signal.Signals, int]]) None
Set the parent-death signal of the current process.
If
sig
is 0 orNone
, the parent-death signal is cleared. Otherwise,sig
specifies the signal that will be sent to the current process when its parent dies.This flag is is cleared in the following cases:
In children of a
fork()
.When
exec()
-ing a binary that is setuid, setgid, or has file capabilities.When the effective UID, effective GID, filesystem UID, or filesystem GID is changed.
See prctl(2) for more details.
- pyprctl.set_seccomp_mode_strict() None
Enable strict seccomp mode.
After this function is is called, the only syscalls that can be made are
read()
,write()
,sigreturn()
, and_exit()
. Making any other syscall will result in SIGKILL being sent to the thread.Note:
sys.exit()
andos._exit()
will call theexit_group()
syscall, not_exit()
.pyprctl
exposes a function_sys_exit()
that calls the_exit()
syscall directly.
- pyprctl.set_securebits(secbits: pyprctl.caps.Secbits) None
Set the current secure bits.
(This requires CAP_SETPCAP.)
- pyprctl.set_timerslack(val: int) None
Set the current thread’s timer slack value.
The timer slack value is used by the kernel to “group” timer expirations that are close to each other. Essentially, it represents the maximum amount of time (in nanoseconds) by which timer expirations (
poll()
,select()
,nanosleep()
, etc.) might be delivered late.Each thread has a “current” timer slack value and a “default” timer slack value. This function manipulates the “current” value; the “default” value cannot be altered. Passing a value of 0 will reset this thread’s “current” value to its “default” value.
See
PR_SET_TIMERSLACK
in prctl(2) for more details.
- pyprctl.set_timing(timing: pyprctl.misc.TimingMethod) None
Set the process timing mode. Currently, only “statistical” process timing is implemented.
- pyprctl.setfsgid(gid: int) None
Set the current thread’s filesystem GID (see setfsgid(2) for details).
This is a helper that calls the
setfsgid()
syscall up to twice to ensure that the filesystem GID has been changed properly. If it hds not, this helper raises aPermissionError
.
- pyprctl.setfsuid(uid: int) None
Set the current thread’s filesystem UID (see setfsuid(2) for details).
This is a helper that calls the
setfsuid()
syscall up to twice to ensure that the filesystem UID has been changed properly. If it has not, this helper raises aPermissionError
.
Capability set objects
Note
This interface is designed after python-prctl
’s interface.
Note
This interface makes it easier to manipulate the permitted/inheritable/effective sets than using CapState
.
However, for “bulk” operations (for example, clearing both the permitted and effective sets), this interface, by design, may result in significantly more syscalls than using CapState
would, since it has to get and then set the full capability state every time something is changed.
If efficiency is important for your application, make sure to take this into account.
Warning
This interface provides methods of performing “bulk” operations on the ambient/bounding sets, but these operations are not performed atomically!
As a result, if the “bulk” methods operating on the ambient/bounding sets fail, these sets will be left in an inconsistent state.
For example, if the current thread’s permitted and inheritable capability sets have CAP_CHOWN but not CAP_SYS_CHROOT, cap_ambient.add(Cap.CHOWN, Cap.SYS_CHROOT)
will successfully add CAP_CHOWN, but it will fail when trying to add CAP_SYS_CHROOT.
It will NOT make any attempt to revert these changes, so the thread will still have CAP_CHOWN raised after the function exits.
In addition to CapState
and the ambient/bounding set manipulation functions, pyprctl
provides five objects that provide alternate ways of interacting with the capability sets:
- pyprctl.cap_permitted
The permitted capability set.
- pyprctl.cap_inheritable
The inheritable capability set.
- pyprctl.cap_effective
The effective capability set.
- pyprctl.cap_ambient
The ambient capability set.
- pyprctl.capbset
The bounding capability set.
These set objects have boolean properties corresponding to each of the capabilities listed for Cap
(except with lowercase names, for example chown
, sys_chroot
, etc.). Accessing any of these properties will check whether that capability is present in the given set, assigning True
to any of these properties will raise them in the given set (if possible), and assigning False
to any of these properties will lower them in the given set. For example:
pyprctl.cap_effective.chown = True
print(pyprctl.cap_effective.chown)
pyprctl.cap_effective.chown = False
The sets also have a few helper methods:
- set.drop(\*caps)
Drop all the given capabilities from this set.
- set.add(\*caps)
Raise all the given capabilities in this set. (This raises a
ValueError
for thecapbset
object.)
- set.limit(\*caps)
Drop all capabilities except the given ones from this set.
- set.replace(\*caps)
“Replace” this capability set with the given capabilities. This is equivalent to the following:
if caps: set.add(*caps) set.limit(*caps)
However, it may be more efficient.
- set.clear()
Remove all capabilities from this set. This is equivalent to
set.limit()
(i.e. with no arguments), but it is easier to understand.
- set.has(\*caps)
Return
True
if the set contains all of the given capabilities, andFalse
if it does not.
Similarly, pyprctl
provides a securebits
object with read/write properties that provide an alternate method of access to the secure bits. This object has a boolean property corresponding to each of the securebits listed for Secbits
(with lowercase names, for example noroot
and keep_caps
).