oslo.privsep/oslo_privsep/capabilities.py
Sven Kieske a2fe33adb8
add new kernel capabilities
CAP_PERFMON, CAP_BPF and CAP_CHECKPOINT_RESTORE
are added.

Change-Id: I5f7758b7284fc16251cccd9114c3ec0e4cc95f93
Signed-off-by: Sven Kieske <kieske@osism.tech>
2023-12-19 16:07:09 +01:00

194 lines
5.3 KiB
Python

# Copyright 2015 Rackspace Hosting
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import enum
import os
import platform
import sys
import cffi
class Capabilities(enum.IntEnum):
# Generated with:
# awk '/^#define CAP_[A-Z_]+[ \t]+[0-9]+/ {print $2,"=",$3}' \
# include/uapi/linux/capability.h
# From the 6.6.6 kernel and the kernel git SHA:0c38b88c3323
# Will need to be refreshed as new capabilites are added to the kernel
CAP_CHOWN = 0
CAP_DAC_OVERRIDE = 1
CAP_DAC_READ_SEARCH = 2
CAP_FOWNER = 3
CAP_FSETID = 4
CAP_KILL = 5
CAP_SETGID = 6
CAP_SETUID = 7
CAP_SETPCAP = 8
CAP_LINUX_IMMUTABLE = 9
CAP_NET_BIND_SERVICE = 10
CAP_NET_BROADCAST = 11
CAP_NET_ADMIN = 12
CAP_NET_RAW = 13
CAP_IPC_LOCK = 14
CAP_IPC_OWNER = 15
CAP_SYS_MODULE = 16
CAP_SYS_RAWIO = 17
CAP_SYS_CHROOT = 18
CAP_SYS_PTRACE = 19
CAP_SYS_PACCT = 20
CAP_SYS_ADMIN = 21
CAP_SYS_BOOT = 22
CAP_SYS_NICE = 23
CAP_SYS_RESOURCE = 24
CAP_SYS_TIME = 25
CAP_SYS_TTY_CONFIG = 26
CAP_MKNOD = 27
CAP_LEASE = 28
CAP_AUDIT_WRITE = 29
CAP_AUDIT_CONTROL = 30
CAP_SETFCAP = 31
CAP_MAC_OVERRIDE = 32
CAP_MAC_ADMIN = 33
CAP_SYSLOG = 34
CAP_WAKE_ALARM = 35
CAP_BLOCK_SUSPEND = 36
CAP_AUDIT_READ = 37
CAP_PERFMON = 38
CAP_BPF = 39
CAP_CHECKPOINT_RESTORE = 40
CAPS_BYNAME = {}
CAPS_BYVALUE = {}
module = sys.modules[__name__]
# Convenience dicts for human readable values
# module attributes for backwards compat/convenience
for c in Capabilities:
CAPS_BYNAME[c.name] = c.value
CAPS_BYVALUE[c.value] = c.name
setattr(module, c.name, c.value)
CDEF = '''
/* Edited highlights from `echo '#include <sys/capability.h>' | gcc -E -` */
#define _LINUX_CAPABILITY_VERSION_2 0x20071026
#define _LINUX_CAPABILITY_U32S_2 2
typedef unsigned int __u32;
typedef struct __user_cap_header_struct {
__u32 version;
int pid;
} *cap_user_header_t;
typedef struct __user_cap_data_struct {
__u32 effective;
__u32 permitted;
__u32 inheritable;
} *cap_user_data_t;
int capset(cap_user_header_t header, const cap_user_data_t data);
int capget(cap_user_header_t header, cap_user_data_t data);
/* Edited highlights from `echo '#include <sys/prctl.h>' | gcc -E -` */
#define PR_GET_KEEPCAPS 7
#define PR_SET_KEEPCAPS 8
int prctl (int __option, ...);
'''
ffi = cffi.FFI()
ffi.cdef(CDEF)
if platform.system() == 'Linux':
# mock.patching crt.* directly seems to upset cffi. Use an
# indirection point here for easier testing.
crt = ffi.dlopen(None)
_prctl = crt.prctl
_capget = crt.capget
_capset = crt.capset
else:
_prctl = None
_capget = None
_capset = None
def set_keepcaps(enable):
"""Set/unset thread's "keep capabilities" flag - see prctl(2)"""
ret = _prctl(crt.PR_SET_KEEPCAPS,
ffi.cast('unsigned long', bool(enable)))
if ret != 0:
errno = ffi.errno
raise OSError(errno, os.strerror(errno))
def drop_all_caps_except(effective, permitted, inheritable):
"""Set (effective, permitted, inheritable) to provided list of caps"""
eff = _caps_to_mask(effective)
prm = _caps_to_mask(permitted)
inh = _caps_to_mask(inheritable)
header = ffi.new('cap_user_header_t',
{'version': crt._LINUX_CAPABILITY_VERSION_2,
'pid': 0})
data = ffi.new('struct __user_cap_data_struct[2]')
data[0].effective = eff & 0xffffffff
data[1].effective = eff >> 32
data[0].permitted = prm & 0xffffffff
data[1].permitted = prm >> 32
data[0].inheritable = inh & 0xffffffff
data[1].inheritable = inh >> 32
ret = _capset(header, data)
if ret != 0:
errno = ffi.errno
raise OSError(errno, os.strerror(errno))
def _mask_to_caps(mask):
"""Convert bitmask to list of set bit offsets"""
return [i for i in range(64) if (1 << i) & mask]
def _caps_to_mask(caps):
"""Convert list of bit offsets to bitmask"""
mask = 0
for cap in caps:
mask |= 1 << cap
return mask
def get_caps():
"""Return (effective, permitted, inheritable) as lists of caps"""
header = ffi.new('cap_user_header_t',
{'version': crt._LINUX_CAPABILITY_VERSION_2,
'pid': 0})
data = ffi.new('struct __user_cap_data_struct[2]')
ret = _capget(header, data)
if ret != 0:
errno = ffi.errno
raise OSError(errno, os.strerror(errno))
return (
_mask_to_caps(data[0].effective |
(data[1].effective << 32)),
_mask_to_caps(data[0].permitted |
(data[1].permitted << 32)),
_mask_to_caps(data[0].inheritable |
(data[1].inheritable << 32)),
)