Почему setuid сбрасывается на execve в альпийском контейнере? - PullRequest
2 голосов
/ 03 мая 2019

Только в альпийском контейнере: при запуске двоичного файла setuid, запускающего другой исполняемый файл (execve(2)), ядро ​​ [1] Кажется, что BusyBox отбрасывает привилегии, полученные setuid , Я думаю, что это может быть сделано из-за соображений безопасности.

Вопрос : Я хотел бы понять, почему это происходит и что за это отвечает?

Я работаю над одноразовым бегуном по сетуиду под названием kamikaze, написанным на rust. kamikaze - это очень простой двоичный файл, который unlink(2) сам по себе и затем запускает новый процесс, используя fork(2) и execve(2).

Основные компоненты:

src/main.rs [a47dedc] : Реализация unlink(2) и процесс появления.

use std::env;

use std::fs;

use std::process::{Command, exit};

fn usage() {
    println!("usage: kamikaze <command> <arguments>");
    exit(1);
}

fn main() {

    // Kill myself
    fs::remove_file(
        env::current_exe().expect("failed to get path to executable")
    ).expect("kamikaze failed");

    let mut args: Vec<String> = env::args().collect();
    match args.len() {
        0 => usage(),
        1 => usage(),
        _ => {
            args.remove(0);
            let mut child = Command::new(args.remove(0))
                .args(&args)
                .spawn()
                .expect("failed to execute process");
            exit(
                child
                    .wait()
                    .expect("wait failed")
                        .code().unwrap()
            );
        },
    }

}

install.sh [a47dedc] : простой установщик, который загружает kamikaze, меняет владельца на root и устанавливает бит setuid.

#!/usr/bin/env sh
set -euo pipefail
REPO="Enteee/kamikaze"
INSTALL="install -m 755 -o root kamikaze-download kamikaze && chmod u+s kamikaze"

curl -s "https://api.github.com/repos/${REPO}/releases/latest" \
   | grep "browser_download_url" \
   | cut -d '"' -f 4 \
   | xargs -n1 curl -s -L --output kamikaze-download

trap 'rm kamikaze-download' EXIT

if [[ $(id -u) -ne 0 ]]; then
  sudo sh -c "${INSTALL}"
else
  eval "${INSTALL}"
fi

Когда я запускаю kamikaze вне контейнера [2] :

$ curl https://raw.githubusercontent.com/Enteee/kamikaze/master/install.sh | sh
$ ./kamikaze ps -f
UID        PID  PPID  C STIME TTY          TIME CMD
root      3223  9587  0 08:17 pts/0    00:00:00 ./kamikaze ps -f
root      3224  3223  0 08:17 pts/0    00:00:00 ps -f

Я получаю ожидаемое поведение. Дочерний процесс (PID=3224) запускается как root. С другой стороны, внутри контейнера [2] :

$ docker build -t kamikaze - <<EOF
  FROM alpine
  RUN set -exuo pipefail \
    && apk add curl \
    && curl https://raw.githubusercontent.com/Enteee/kamikaze/master/install.sh | sh

  USER nobody
  CMD ["/kamikaze", "ps"]
EOF
$ docker run kamikaze
PID   USER     TIME  COMMAND
    1 root      0:00 /kamikaze ps
    6 nobody    0:00 ps

ps работает как nobody.


[1] Сначала я подумал, что это из-за некоторого механизма безопасности, реализованного в Docker и ядре Linux. Но после глубокого погружения в Docker Security , NO_NEW_PRIVILEGES и seccomp(2) я наконец понял, что BusyBox просто отбрасывает привилегии.

[2] kamikaze [1.0.0] исправлено и изменено это поведение. Поэтому этот пример больше не работает. Для воспроизведения примера используйте релиз kamikaze [0.0.0] .

1 Ответ

0 голосов
/ 05 мая 2019

BusyBox, который реализует команду ps в alpine, отбрасывает привилегии, полученные setuid, путем установки действительного идентификатора пользователя для реального идентификатора пользователя.

libbb / appletlib.c [b097a84] :

    } else if (APPLET_SUID(applet_no) == BB_SUID_DROP) {
        /*
         * Drop all privileges.
         *
         * Don't check for errors: in normal use, they are impossible,
         * and in special cases, exiting is harmful. Example:
         * 'unshare --user' when user's shell is also from busybox.
         *
         * 'unshare --user' creates a new user namespace without any
         * uid mappings. Thus, busybox binary is setuid nobody:nogroup
         * within the namespace, as that is the only user. However,
         * since no uids are mapped, calls to setgid/setuid
         * fail (even though they would do nothing).
         */
        setgid(rgid);
        setuid(ruid);
    }

procps / ps.c [b097a84] : определяет BB_SUID_DROP.

//                 APPLET_NOEXEC:name    main location    suid_type     help
//applet:IF_PS(    APPLET_NOEXEC(ps,     ps,  BB_DIR_BIN, BB_SUID_DROP, ps))
//applet:IF_MINIPS(APPLET_NOEXEC(minips, ps,  BB_DIR_BIN, BB_SUID_DROP, ps))

Исправить это было просто.kamikaze просто нужно установить действительный идентификатор пользователя для действующего идентификатора пользователя до execve(2).

src / main.rs [f4c5501] :

extern crate exec;
extern crate users;

use std::env;

use std::fs;

use std::process::exit;

use users::{get_effective_uid, get_effective_gid};
use users::switch::{set_current_uid, set_current_gid};

fn usage() {
    println!("usage: kamikaze <command> <arguments>");
}

fn main() {

    // Kill myself
    fs::remove_file(
        env::current_exe().expect("failed to get path to executable")
    ).expect("kamikaze failed");

    set_current_uid(
        get_effective_uid()
    ).expect("failed setting current uid");

    set_current_gid(
        get_effective_gid()
    ).expect("failed setting current gid");

    let mut args: Vec<String> = env::args().collect();
    match args.len() {
        0 => usage(),
        1 => usage(),
        _ => {
            args.remove(0);
            let err = exec::Command::new(args.remove(0))
                .args(&args)
                .exec();
            println!("Error: {}", err);
        },
    }
    // Should never get here
    exit(1);
}

с недавно выпущенным kamikaze [1.0.0] теперь мы получаем:

$ docker build -t kamikaze - <<EOF
  FROM alpine
  RUN set -exuo pipefail \
    && apk add curl \
    && curl https://raw.githubusercontent.com/Enteee/kamikaze/master/install.sh | sh

  USER nobody
  CMD ["/kamikaze", "ps"]
EOF
$ docker run kamikaze
PID   USER     TIME  COMMAND
    1 root      0:00 ps
...