No Linux, o conceito de namespace é fundamental para o isolamento de recursos entre processos. De forma semelhante a como um namespace em C++ encapsula identificadores, um namespace do kernel delimita uma visibilidade: processos dentro de um namespace só enxergam os recursos pertencentes àquele mesmo namespace, estando cegos a recursos externos. Esse mecanismo é a base para tecnologias como containers.
Existem oito tipos de namespaces no Linux, incluindo mount, pid, network, user e ipc. A manipulação programática de namespaces é realizada principalmente através de três system calls: clone, unshare e setns. Para lidar com mount namespaces, as chamadas mount e umount2 também são essenciais.
As Principais System Calls
1. clone
A system call clone é uma versão avançada de fork, oferecendo controle granular sobre o que é compartilhado entre o processo pai e o filho. Na verdade, fork e pthread_create são implementados internamente usando clone.
int clone(int (*fn)(void *), void *stack, int flags, void *arg);
- fn: Função de entrada para o novo processo/thread criado.
- stack: Apontador para a pilha do novo processo/thread. Para threads (compartilhamento de espaço de endereçamento), a pilha deve ser alocada explicitamente. Para processos, o Copy-On-Write (COW) gerencia isso automaticamente.
- flags: Este é o parâmetro chave. Os flags determinam se recursos são compartilhados ou isolados.
Flags de isolamento (Namespace):
CLONE_NEWPID: Cria um novo PID namespace. O processo filho torna-se o PID 1 dentro dele.CLONE_NEWNS: Cria um novo mount namespace. O filho recebe uma cópia da árvore de montagens do pai.CLONE_NEWNET: Cria um novo network namespace, com apenas a interface de loopback.CLONE_NEWUSER: Cria um novo user namespace. É único por permitir que usuários não privilegiados o criem, concedendo a eles capacidades completas dentro do novo namespace.
Flags de compartilhamento de recursos (para diferenciação processo/thread):
CLONE_VM: Compartilha o espaço de endereçamento (cria uma thread).CLONE_FILES: Compartilha a tabela de descritores de arquivo.CLONE_THREAD: Coloca no mesmo grupo de threads.
2. unshare
A system call unshare desassocia o processo atual de partes de seu contexto de execução e cria novas instâncias de namespaces para essas partes, sem criar um novo processo.
int unshare(int flags);
Os flags aceitos são os mesmos de clone (ex: CLONE_NEWPID, CLONE_NEWNET). No entanto, para CLONE_NEWPID, o processo que chama unshare não muda de namespace; somente seus futuros filhos herdarão o novo PID namespace.
3. setns
A system call setns faz com que o processo atual se junte a um namespace já existente, referenciado por um file descriptor.
int setns(int fd, int nstype);
- fd: Descritor de arquivo para um arquivo em
/proc/[pid]/ns/(ex:/proc/123/ns/net). Abrir esse arquivo fornece uma referência ao objeto de namespace do kernel. - nstype: Tipo esperado do namespace (para validação). Passar 0 pula a verificação.
Semelhante ao unshare, a mudança para um PID namespace existente só terá efeito para os filhos do processo.
Montagem de Sistemas de Arquivos em Mount Namespaces
mount
Uma vez dentro de um novo mount namespace, a system call mount é usada para modificar sua árvore de montagem.
int mount(const char *source, const char *target, const char *filesystemtype,
unsigned long mountflags, const void *data);
- source: Dispositivo de bloco, sistema de arquivos virtual (tmpfs, proc) ou diretório origem (para bind mount).
- target: Ponto de montagem (deve existir).
- filesystemtype: Tipo do sistema de arquivos (ex: "ext4", "tmpfs", "proc"). Para bind mounts, pode ser NULL.
- mountflags: Flags como
MS_BIND(bind mount),MS_REC(recursivo),MS_RDONLY(somente leitura),MS_NOSUID(ignora SUID/SGID).
umount2
Desmonta um sistema de arquivos.
int umount2(const char *target, int flags);
- target: Ponto de montagem a ser desmontado.
- flags:
MNT_DETACH(desmontagem preguiçosa) ouMNT_FORCE(forçada).
Exemplo Prático: Construindo uma Sandbox com Mount Namespace
Um cenário comum é a construção de uma sandbox (ex: para um sistema de OJ). O processo filho (na sandbox) segue estes passos:
1. Isolar a propagação de montagens:
mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL);
2. Montar pontos essenciais com bind mounts e sistemas de arquivos virtuais:
// Montar diretórios necessários (ex: /bin, /lib) a partir do host
mount("/host/bin", "/newroot/bin", NULL, MS_BIND | MS_REC, NULL);
// Montar sistemas de arquivos virtuais isolados
mount("tmpfs", "/newroot/tmp", "tmpfs", MS_NOSUID | MS_NODEV, "size=100m");
mount("proc", "/newroot/proc", "proc", MS_NOSUID | MS_NODEV | MS_NOEXEC, NULL);
3. Alterar a raiz do sistema de arquivos (pivot_root):
// Garantir que /newroot é um ponto de montagem
mount("/newroot", "/newroot", NULL, MS_BIND | MS_REC, NULL);
// Criar diretório para o antigo root
mkdir("/newroot/.old_root", 0700);
// Trocar o root
syscall(SYS_pivot_root, "/newroot", "/newroot/.old_root");
// Mudar para o novo root
chdir("/");
// Desmontar e remover o antigo root
umount2("/.old_root", MNT_DETACH);
rmdir("/.old_root");
Após essas operações, o processo filho encontra-se em um mount namespace isolado, com uma visão do sistema de arquivos limitada aos recursos explicitamente montados.