AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / unix / Perguntas / 702980
Accepted
tobiasBora
tobiasBora
Asked: 2022-05-18 22:19:03 +0800 CST2022-05-18 22:19:03 +0800 CST 2022-05-18 22:19:03 +0800 CST

Espaços de nomes de usuário: como montar uma pasta apenas para um determinado programa

  • 772

Eu gostaria de falsificar um sistema FHS em um sistema não FHS (NixOs) sem acesso root. Para esse fim, preciso montar algumas pastas na raiz (como montar /tmp/mylibem /lib) usando usernamespaces (não vejo outra solução).

Infelizmente, não consigo encontrar como fazê-lo funcionar: tentei seguir este tutorial , mas quando copio o código ele falha (não consigo nem iniciar um bash):

$ gcc userns_child_exec.c -lcap -o userns_child_exec
$ id
uid=1000(myname) gid=100(users) groups=100(users),1(wheel),17(audio),20(lp),57(networkmanager),59(scanner),131(docker),998(vboxusers),999(adbusers)

$ ./userns_child_exec -U -M '0 1000 1' -G '0 100 1' bash
write /proc/535313/gid_map: Operation not permitted
bash: initialize_job_control: no job control in background: Bad file descriptor

[nix-shell:~/Documents/Logiciels/Nix_bidouille/2022_04_26_-_nix_fake_FHS_user_namespace/demo]$ 
[root@bestos:~/Documents/Logiciels/Nix_bidouille/2022_04_26_-_nix_fake_FHS_user_namespace/demo]# 
exit

(observe que o prompt para o bash é exibido, mas não consigo digitar nada, ele fecha diretamente)

Alguma ideia de como fazer isso funcionar?

Código:

/* userns_child_exec.c

   Copyright 2013, Michael Kerrisk
   Licensed under GNU General Public License v2 or later

   Create a child process that executes a shell command in new
   namespace(s); allow UID and GID mappings to be specified when
   creating a user namespace.
*/
#define _GNU_SOURCE
#include <sched.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <signal.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <limits.h>
#include <errno.h>

/* A simple error-handling function: print an error message based
   on the value in 'errno' and terminate the calling process */

#define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
                        } while (0)

struct child_args {
    char **argv;        /* Command to be executed by child, with arguments */
    int    pipe_fd[2];  /* Pipe used to synchronize parent and child */
};

static int verbose;

static void
usage(char *pname)
{
    fprintf(stderr, "Usage: %s [options] cmd [arg...]\n\n", pname);
    fprintf(stderr, "Create a child process that executes a shell command "
            "in a new user namespace,\n"
            "and possibly also other new namespace(s).\n\n");
    fprintf(stderr, "Options can be:\n\n");
#define fpe(str) fprintf(stderr, "    %s", str);
    fpe("-i          New IPC namespace\n");
    fpe("-m          New mount namespace\n");
    fpe("-n          New network namespace\n");
    fpe("-p          New PID namespace\n");
    fpe("-u          New UTS namespace\n");
    fpe("-U          New user namespace\n");
    fpe("-M uid_map  Specify UID map for user namespace\n");
    fpe("-G gid_map  Specify GID map for user namespace\n");
    fpe("            If -M or -G is specified, -U is required\n");
    fpe("-v          Display verbose messages\n");
    fpe("\n");
    fpe("Map strings for -M and -G consist of records of the form:\n");
    fpe("\n");
    fpe("    ID-inside-ns   ID-outside-ns   len\n");
    fpe("\n");
    fpe("A map string can contain multiple records, separated by commas;\n");
    fpe("the commas are replaced by newlines before writing to map files.\n");

    exit(EXIT_FAILURE);
}

/* Update the mapping file 'map_file', with the value provided in
   'mapping', a string that defines a UID or GID mapping. A UID or
   GID mapping consists of one or more newline-delimited records
   of the form:

       ID_inside-ns    ID-outside-ns   length

   Requiring the user to supply a string that contains newlines is
   of course inconvenient for command-line use. Thus, we permit the
   use of commas to delimit records in this string, and replace them
   with newlines before writing the string to the file. */

static void
update_map(char *mapping, char *map_file)
{
    int fd, j;
    size_t map_len;     /* Length of 'mapping' */

    /* Replace commas in mapping string with newlines */

    map_len = strlen(mapping);
    for (j = 0; j < map_len; j++)
        if (mapping[j] == ',')
            mapping[j] = '\n';

    fd = open(map_file, O_RDWR);
    if (fd == -1) {
        fprintf(stderr, "open %s: %s\n", map_file, strerror(errno));
        exit(EXIT_FAILURE);
    }

    if (write(fd, mapping, map_len) != map_len) {
        fprintf(stderr, "write %s: %s\n", map_file, strerror(errno));
        exit(EXIT_FAILURE);
    }

    close(fd);
}

static int              /* Start function for cloned child */
childFunc(void *arg)
{
    struct child_args *args = (struct child_args *) arg;
    char ch;

    /* Wait until the parent has updated the UID and GID mappings. See
       the comment in main(). We wait for end of file on a pipe that will
       be closed by the parent process once it has updated the mappings. */

    close(args->pipe_fd[1]);    /* Close our descriptor for the write end
                                   of the pipe so that we see EOF when
                                   parent closes its descriptor */
    if (read(args->pipe_fd[0], &ch, 1) != 0) {
        fprintf(stderr, "Failure in child: read from pipe returned != 0\n");
        exit(EXIT_FAILURE);
    }

    /* Execute a shell command */

    execvp(args->argv[0], args->argv);
    errExit("execvp");
}

#define STACK_SIZE (1024 * 1024)

static char child_stack[STACK_SIZE];    /* Space for child's stack */

int
main(int argc, char *argv[])
{
    int flags, opt;
    pid_t child_pid;
    struct child_args args;
    char *uid_map, *gid_map;
    char map_path[PATH_MAX];

    /* Parse command-line options. The initial '+' character in
       the final getopt() argument prevents GNU-style permutation
       of command-line options. That's useful, since sometimes
       the 'command' to be executed by this program itself
       has command-line options. We don't want getopt() to treat
       those as options to this program. */

    flags = 0;
    verbose = 0;
    gid_map = NULL;
    uid_map = NULL;
    while ((opt = getopt(argc, argv, "+imnpuUM:G:v")) != -1) {
        switch (opt) {
        case 'i': flags |= CLONE_NEWIPC;        break;
        case 'm': flags |= CLONE_NEWNS;         break;
        case 'n': flags |= CLONE_NEWNET;        break;
        case 'p': flags |= CLONE_NEWPID;        break;
        case 'u': flags |= CLONE_NEWUTS;        break;
        case 'v': verbose = 1;                  break;
        case 'M': uid_map = optarg;             break;
        case 'G': gid_map = optarg;             break;
        case 'U': flags |= CLONE_NEWUSER;       break;
        default:  usage(argv[0]);
        }
    }

    /* -M or -G without -U is nonsensical */

    if ((uid_map != NULL || gid_map != NULL) &&
            !(flags & CLONE_NEWUSER))
        usage(argv[0]);

    args.argv = &argv[optind];

    /* We use a pipe to synchronize the parent and child, in order to
       ensure that the parent sets the UID and GID maps before the child
       calls execve(). This ensures that the child maintains its
       capabilities during the execve() in the common case where we
       want to map the child's effective user ID to 0 in the new user
       namespace. Without this synchronization, the child would lose
       its capabilities if it performed an execve() with nonzero
       user IDs (see the capabilities(7) man page for details of the
       transformation of a process's capabilities during execve()). */

    if (pipe(args.pipe_fd) == -1)
        errExit("pipe");

    /* Create the child in new namespace(s) */

    child_pid = clone(childFunc, child_stack + STACK_SIZE,
                      flags | SIGCHLD, &args);
    if (child_pid == -1)
        errExit("clone");

    /* Parent falls through to here */

    if (verbose)
        printf("%s: PID of child created by clone() is %ld\n",
                argv[0], (long) child_pid);

    /* Update the UID and GID maps in the child */

    if (uid_map != NULL) {
        snprintf(map_path, PATH_MAX, "/proc/%ld/uid_map",
                (long) child_pid);
        update_map(uid_map, map_path);
    }
    if (gid_map != NULL) {
        snprintf(map_path, PATH_MAX, "/proc/%ld/gid_map",
                (long) child_pid);
        update_map(gid_map, map_path);
    }

    /* Close the write end of the pipe, to signal to the child that we
       have updated the UID and GID maps */

    close(args.pipe_fd[1]);

    if (waitpid(child_pid, NULL, 0) == -1)      /* Wait for child */
        errExit("waitpid");

    if (verbose)
        printf("%s: terminating\n", argv[0]);

    exit(EXIT_SUCCESS);
}

EDITAR

Na verdade, é bem estranho: o erro aparece ao escrever o grupo, mas funcionou para o uid:

[leo@bestos:~]$ cat /proc/582197/gid_map 

[leo@bestos:~]$ cat /proc/582197/uid_map 
         0       1000          1

[leo@bestos:~]$ ll /proc/582197/gid_map 
-rw-r--r-- 1 leo users 0 mai   18 09:09 /proc/582197/gid_map

[leo@bestos:~]$ ll /proc/582197/uid_map 
-rw-r--r-- 1 leo users 0 mai   18 09:09 /proc/582197/uid_map

mount userns
  • 2 2 respostas
  • 178 Views

2 respostas

  • Voted
  1. Best Answer
    A.B
    2022-05-18T23:21:35+08:002022-05-18T23:21:35+08:00

    O tutorial que você está lendo foi criado em 2013 antes de uma importante restrição adicional ser adicionada ao tratamento de mapeamentos GID no kernel 3.19 em 2015. Conforme man user_namespaces:

    Gravar "deny" no arquivo /proc/[pid]/setgroups antes de gravar em /proc/[pid]/gid_map desativará permanentemente setgroups (2) em um namespace de usuário e permitirá gravar em /proc/[pid]/gid_map sem tendo o recurso CAP_SETGID no namespace do usuário pai.

    O arquivo /proc/[pid]/setgroups foi adicionado no Linux 3.19 , mas foi retroportado para muitas séries de kernel estáveis ​​anteriores, porque aborda um problema de segurança. O problema dizia respeito a arquivos com permissões como "rwx---rwx". Esses arquivos dão menos permissões para "grupo" do que para "outros". Isso significa que a eliminação de grupos usando setgroups(2) pode permitir um acesso ao arquivo de processo que ele não tinha anteriormente. Antes da existência de namespaces de usuário, isso não era uma preocupação [...] Isso permitiu que usuários anteriormente sem privilégios eliminassem grupos e, assim, ganhassem acesso a arquivos que não tinham anteriormente. [...]

    Portanto, você deve adicionar código para gravar a palavra denyno arquivo cujo nome está snprintf(map_path, PATH_MAX, "/proc/%ld/setgroups", (long) child_pid);logo antes de gravar em gid_map.

    Todo o código pode ser substituído por este comando onipresente:

    unshare --user --map-root-user --mount -- bash
    

    (que tem um implícito --setgroups=deny)

    Também sem privilégios apenas UM uid/gid pode ser mapeado. Assim, uma vez que as montagens são feitas, a única opção possível para simular o usuário original, embora incompletamente, é mapear de volta para o usuário original, o que pode ser feito com uma versão recente unsharetambém com um segundo namespace de usuário em cascata daquele que acabou de ser compartilhado:

    # unshare --user --map-user=1000 --map-group=100 -- bash
    

    então haverá UM uid neste namespace. mesmo root não existe mais (e é visto mapeado nobodycomo qualquer outro uid não mapeado).


    Observação

    Existem outras interações com outros namespaces e recursos, aqui está um exemplo :

    Manter CAP_SYS_ADMINdentro do namespace do usuário que possui o namespace PID de um processo permite (desde o Linux 3.8) que esse processo monte sistemas de arquivos /proc .

    Portanto, adicionar --pid --forkpara cumprir a restrição acima permite montar /procsobre o existente se isso for necessário mais tarde, mas geralmente isso é necessário apenas ao usar --pidem primeiro lugar (e isso também pode ser feito convenientemente adicionando --mount-proc).

    Da mesma forma --net, é necessário montar /syspor causa de suas interações com namespaces de rede.


    Juntando tudo isso para substituir /libpelo conteúdo do exemplo /tmp/odo OP :

    unshare --user --map-root-user --mount -- \
        sh -c 'mount --bind /tmp/o /lib; exec unshare --user --map-user=1000 --map-group=100 -- bash'
    

    Nota: Não é mais possível usar a maioria dos comandos privilegiados corretamente depois de ter feito o primeiro mapeamento: ou há o único UID 0 disponível no namespace do usuário, ou há o único UID 1000 disponível no próximo namespace (aninhado). Como os comandos privilegiados lidam com a transição entre dois UIDs (um deles geralmente root) e um não está disponível, geralmente falhará em algum syscall com EINVAL.

    Para fazer melhor do que isso, é preciso, em primeiro lugar, a ajuda de comandos privilegiados e acesso root para configurar direitos adicionais. Por exemplo, os comandos setuid root newuidmape newgidmapque geralmente são necessários para inicializar um contêiner completo de um usuário sem privilégios.

    • 1
  2. tobiasBora
    2022-05-21T11:30:11+08:002022-05-21T11:30:11+08:00

    Apenas para completar a ótima resposta de AB e deixar um comentário escrito por AB mais visível, se a pasta for montada em uma pasta que ainda não existe, é possível usar chroot dentro do unshare:

    $ unshare --user --map-root-user --mount-proc --pid --fork
    # cd /tmp/ && mkdir mychroot && cd mychroot
    # for folder in $(ls / | grep -v sys); do echo "$folder"; mkdir "./$folder"; mount --rbind "/$folder" "./$folder"; done; mkdir sys; mount --rbind /sys sys/
    # mkdir lib
    # chroot .
    # ls /
    bin  boot  dev  etc  home  lib  mnt  nix  opt  proc  root  run  srv  sys  tmp  usr  var
    

    Note que aqui você não pode usar sudo, tudo que você fará será como um usuário normal. Vou tentar ver se newuidmappode ajudar aqui.

    (Os usuários do NB NixOs podem precisar usar /run/current-system/sw/bin/mountem vez de mount https://github.com/NixOS/nixpkgs/issues/42117 )

    • 0

relate perguntas

  • Montando permanentemente um diretório com LVM

  • Impedir que montagens NFS quebradas bloqueiem um diretório no solaris 11.3?

  • Como montar partições de unidade zfs no solaris 11.3

  • Bloqueando montagem syscall

  • montar lan hdd no linux fedora

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Possível firmware ausente /lib/firmware/i915/* para o módulo i915

    • 3 respostas
  • Marko Smith

    Falha ao buscar o repositório de backports jessie

    • 4 respostas
  • Marko Smith

    Como exportar uma chave privada GPG e uma chave pública para um arquivo

    • 4 respostas
  • Marko Smith

    Como podemos executar um comando armazenado em uma variável?

    • 5 respostas
  • Marko Smith

    Como configurar o systemd-resolved e o systemd-networkd para usar o servidor DNS local para resolver domínios locais e o servidor DNS remoto para domínios remotos?

    • 3 respostas
  • Marko Smith

    apt-get update error no Kali Linux após a atualização do dist [duplicado]

    • 2 respostas
  • Marko Smith

    Como ver as últimas linhas x do log de serviço systemctl

    • 5 respostas
  • Marko Smith

    Nano - pule para o final do arquivo

    • 8 respostas
  • Marko Smith

    erro grub: você precisa carregar o kernel primeiro

    • 4 respostas
  • Marko Smith

    Como baixar o pacote não instalá-lo com o comando apt-get?

    • 7 respostas
  • Martin Hope
    user12345 Falha ao buscar o repositório de backports jessie 2019-03-27 04:39:28 +0800 CST
  • Martin Hope
    Carl Por que a maioria dos exemplos do systemd contém WantedBy=multi-user.target? 2019-03-15 11:49:25 +0800 CST
  • Martin Hope
    rocky Como exportar uma chave privada GPG e uma chave pública para um arquivo 2018-11-16 05:36:15 +0800 CST
  • Martin Hope
    Evan Carroll status systemctl mostra: "Estado: degradado" 2018-06-03 18:48:17 +0800 CST
  • Martin Hope
    Tim Como podemos executar um comando armazenado em uma variável? 2018-05-21 04:46:29 +0800 CST
  • Martin Hope
    Ankur S Por que /dev/null é um arquivo? Por que sua função não é implementada como um programa simples? 2018-04-17 07:28:04 +0800 CST
  • Martin Hope
    user3191334 Como ver as últimas linhas x do log de serviço systemctl 2018-02-07 00:14:16 +0800 CST
  • Martin Hope
    Marko Pacak Nano - pule para o final do arquivo 2018-02-01 01:53:03 +0800 CST
  • Martin Hope
    Kidburla Por que verdadeiro e falso são tão grandes? 2018-01-26 12:14:47 +0800 CST
  • Martin Hope
    Christos Baziotis Substitua a string em um arquivo de texto enorme (70 GB), uma linha 2017-12-30 06:58:33 +0800 CST

Hot tag

linux bash debian shell-script text-processing ubuntu centos shell awk ssh

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve