Недавно я пишу программы с libcap
и libseccomp
и заметил проблему при их совместном использовании.
В следующем минимальном воспроизводимом примере я сначала установил для текущего процесса возможность только P(inheritable) = CAP_NET_RAW
, а остальные наборы возможностей были очищены. Затем я инициализирую фильтр seccomp с действием SCMP_ACT_ALLOW
(по умолчанию разрешены все системные вызовы), загружаю его и очищаю.
Наконец, эта программа выводит свои текущие возможности и выполняет capsh --print
, чтобы показать свои возможности после выполнения execve()
.
#include <linux/capability.h>
#include <sys/capability.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <seccomp.h>
#define CAPSH "/usr/sbin/capsh"
int main(void) {
cap_value_t net_raw = CAP_NET_RAW;
cap_t caps = cap_init();
cap_set_flag(caps, CAP_INHERITABLE, 1, &net_raw, CAP_SET);
if (cap_set_proc(caps)) {
perror("cap_set_proc");
}
cap_free(caps);
scmp_filter_ctx ctx;
if ((ctx = seccomp_init(SCMP_ACT_ALLOW)) == NULL) {
perror("seccomp_init");
}
int rc = 0;
rc = seccomp_load(ctx); // comment this line later
if (rc < 0)
perror("seccomp_load");
seccomp_release(ctx);
ssize_t y = 0;
printf("Process capabilities: %s\n", cap_to_text(cap_get_proc(), &y));
char *argv[] = {
CAPSH,
"--print",
NULL
};
execve(CAPSH, argv, NULL);
return -1;
}
Скомпилируйте с -lcap
и -lseccomp
, запустите под пользователем root (UID=EUID=0) и получите следующее:
Process capabilities: = cap_net_raw+i
Current: = cap_net_raw+i
Bounding set =cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read
Securebits: 00/0x0/1'b0
secure-noroot: no (unlocked)
secure-no-suid-fixup: no (unlocked)
secure-keep-caps: no (unlocked)
uid=0(root)
gid=0(root)
groups=0(root)
Это указывает на то, что текущий процесс и выполняемые capsh
все имеют наследуемый набор, а не только пустой. Однако, если я прокомментирую строку rc = seccomp_load(ctx);
, все будет по-другому:
Process capabilities: = cap_net_raw+i
Current: = cap_net_raw+eip cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read+ep
Bounding set =cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read
Securebits: 00/0x0/1'b0
secure-noroot: no (unlocked)
secure-no-suid-fixup: no (unlocked)
secure-keep-caps: no (unlocked)
uid=0(root)
gid=0(root)
groups=0(root)
До execve()
результат такой же, как и выше. Но после этого все остальные возможности возвращаются в разрешенные и эффективные наборы.
Я просмотрел capabilities(7) и обнаружил следующее: в руководстве:
Capabilities and execution of programs by root
In order to mirror traditional UNIX semantics, the kernel performs
special treatment of file capabilities when a process with UID 0
(root) executes a program and when a set-user-ID-root program is exe‐
cuted.
After having performed any changes to the process effective ID that
were triggered by the set-user-ID mode bit of the binary—e.g.,
switching the effective user ID to 0 (root) because a set-user-ID-
root program was executed—the kernel calculates the file capability
sets as follows:
1. If the real or effective user ID of the process is 0 (root), then
the file inheritable and permitted sets are ignored; instead they
are notionally considered to be all ones (i.e., all capabilities
enabled). (There is one exception to this behavior, described
below in Set-user-ID-root programs that have file capabilities.)
2. If the effective user ID of the process is 0 (root) or the file
effective bit is in fact enabled, then the file effective bit is
notionally defined to be one (enabled).
These notional values for the file's capability sets are then used as
described above to calculate the transformation of the process's
capabilities during execve(2).
Thus, when a process with nonzero UIDs execve(2)s a set-user-ID-root
program that does not have capabilities attached, or when a process
whose real and effective UIDs are zero execve(2)s a program, the cal‐
culation of the process's new permitted capabilities simplifies to:
P'(permitted) = P(inheritable) | P(bounding)
P'(effective) = P'(permitted)
Consequently, the process gains all capabilities in its permitted and
effective capability sets, except those masked out by the capability
bounding set. (In the calculation of P'(permitted), the P'(ambient)
term can be simplified away because it is by definition a proper sub‐
set of P(inheritable).)
The special treatments of user ID 0 (root) described in this subsec‐
tion can be disabled using the securebits mechanism described below.
И вот что меня смущает: наследуемое множество не пусто, и по упрощенному правилу разрешенные и эффективные множества не должны быть пустыми. Однако загрузка фильтра seccomp, похоже, нарушает это правило.