Como impedir que um processo grave arquivos

13

Quero executar um comando no Linux de uma maneira que não possa criar ou abrir nenhum arquivo para gravação. Ele ainda deve ler os arquivos normalmente (portanto, um chroot vazio não é uma opção) e ainda pode gravar em arquivos já abertos (especialmente stdout).

Pontos de bônus se a gravação de arquivos em determinados diretórios (ou seja, o diretório atual) ainda for possível.

Estou procurando uma solução que seja local do processo, ou seja, não envolva a configuração de coisas como AppArmor ou SELinux para todo o sistema, nem privilégios de root. No entanto, pode envolver a instalação de seus módulos do kernel.

Eu estava olhando para os recursos e estes teriam sido agradáveis ​​e fáceis, se houvesse um recurso para criar arquivos. O ulimit é outra abordagem que seria conveniente se abordasse esse caso de uso.

Joachim Breitner
fonte
Muitos programas assumem que são capazes de gravar arquivos normalmente (e falham de maneiras estranhas quando não conseguem). straceinforma quais arquivos o programa está abrindo. Por que você quer fazer isso? É um programa específico ou você deseja que seja testado ou algo mais? Você pode executar o programa como um usuário / grupo que não tem permissão para gravar em quase todos os lugares, exceto no diretório atual? As distribuições modernas do Linux usam a idéia de um grupo para cada usuário, portanto, isso deve ser relativamente fácil de configurar.
21133 vonbrand
É um programa especial (Isabelle) que já interpreta o código de maneira um tanto segura (sem execução arbitrária de código), mas ainda permite que o código crie arquivos em locais arbitrários. Como o código não é confiável, desejo impedir que isso aconteça (abortando o programa). O programa já é executado como um usuário especial, mas eu me sentiria mais seguro se o código não pudesse bloquear, digamos, / tmp ou lugares semelhantes.
Joachim Breitner
Você pode adicionar um novo usuário para executar o aplicativo.
ctrl-alt-delor 16/04/2015

Respostas:

9

Que tal criar um chroot vazio e montar o sistema de arquivos principal como somente leitura dentro do chroot?

Provavelmente deve ser algo assim para criar uma montagem de ligação somente leitura:

mount --bind /foo/ /path/to/chroot/
mount -o remount,ro /path/to/chroot/

Você pode montar outros diretórios aos quais deseja que a cadeia também tenha acesso de gravação. Tenha cuidado se você precisar vincular diretórios especiais de montagem (/ dev /, / proc /, / sys /), pois sua montagem como está pode ser insegura.

Lie Ryan
fonte
Novamente, precisa de privilégios de root e outras "configurações globais". Mas uma opção sim.
Joachim Breitner
O /foo/caminho para o sistema de arquivos principal?
Wayne Conrad
5

Parece que a ferramenta certa para este trabalho é fseccompbaseada no sync-ignoringcódigo f de Bastian Blank, criei um arquivo relativamente pequeno que faz com que todos os seus filhos não consigam abrir um arquivo para escrever:

/*
 * Copyright (C) 2013 Joachim Breitner <[email protected]>
 *
 * Based on code Copyright (C) 2013 Bastian Blank <[email protected]>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#define _GNU_SOURCE 1
#include <errno.h>
#include <fcntl.h>
#include <seccomp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define filter_rule_add(action, syscall, count, ...) \
  if (seccomp_rule_add(filter, action, syscall, count, ##__VA_ARGS__)) abort();

static int filter_init(void)
{
  scmp_filter_ctx filter;

  if (!(filter = seccomp_init(SCMP_ACT_ALLOW))) abort();
  if (seccomp_attr_set(filter, SCMP_FLTATR_CTL_NNP, 1)) abort();
  filter_rule_add(SCMP_ACT_ERRNO(EACCES), SCMP_SYS(open), 1, SCMP_A1(SCMP_CMP_MASKED_EQ, O_WRONLY, O_WRONLY));
  filter_rule_add(SCMP_ACT_ERRNO(EACCES), SCMP_SYS(open), 1, SCMP_A1(SCMP_CMP_MASKED_EQ, O_RDWR, O_RDWR));
  return seccomp_load(filter);
}

int main(__attribute__((unused)) int argc, char *argv[])
{
  if (argc <= 1)
  {
    fprintf(stderr, "usage: %s COMMAND [ARG]...\n", argv[0]);
    return 2;
  }

  if (filter_init())
  {
    fprintf(stderr, "%s: can't initialize seccomp filter\n", argv[0]);
    return 1;
  }

  execvp(argv[1], &argv[1]);

  if (errno == ENOENT)
  {
    fprintf(stderr, "%s: command not found: %s\n", argv[0], argv[1]);
    return 127;
  }

  fprintf(stderr, "%s: failed to execute: %s: %s\n", argv[0], argv[1], strerror(errno));
  return 1;
}

Aqui você pode ver que ainda é possível ler arquivos:

[jojo@kirk:1] Wed, der 06.03.2013 um 12:58 Uhr Keep Smiling :-)
> ls test
ls: cannot access test: No such file or directory
> echo foo > test
bash: test: Permission denied
> ls test
ls: cannot access test: No such file or directory
> touch test
touch: cannot touch 'test': Permission denied
> head -n 1 no-writes.c # reading still works
/*

Ele não impede a exclusão de arquivos, a sua movimentação ou outras operações de arquivos além da abertura, mas que podem ser adicionadas.

Uma ferramenta que permite isso sem precisar escrever código C é syscall_limiter .

Joachim Breitner
fonte
4
Observe que a abordagem segura é colocar na lista branca os syscalls, não na lista negra deles. Se muito for negado, ajudantes externos sem caixa de entrada podem ser usados ​​para ajudar o programa. Com o LD_PRELOAD, esses auxiliares podem se tornar transparentes ao programa que estamos executando.
Vi.
4

Você consideraria escrever um substituto para open(…)funcionar e carregá-lo usando LD_PRELOAD?

Leonid
fonte
2
Você provavelmente quer dizer open... Bem, eu consideraria usar uma solução existente que use essa abordagem, sim.
Joachim Breitner
2
Existe algo assim em github.com/certik/restrict , mas é configurado pela compilação e não parece estar sendo amplamente utilizado.
Joachim Breitner
Sim, desculpe, meu erro, atualizando a resposta ... Mas parece-me que você terá que substituir uma pela write(…)também.
Leonid
Quanto ao github.com/certik/restrict , sim, você está completamente certo.
Leonid
3

A solução mais simples é provavelmente um programa wrapper que cria um novo espaço para nome do sistema de arquivos com os sistemas de arquivos relevantes montados somente leitura e depois executa o programa que você está tentando restringir.

É o que systemdfaz quando você usa ReadOnlyDirectories=para marcar determinados diretórios como somente leitura para um serviço. Há também um unsharecomando util-linuxque pode fazer o trabalho de criar um novo espaço para nome, para que você possa fazer algo como:

unshare -m <wrapper>

onde wrapperseria necessário apenas remontar os sistemas de arquivos conforme necessário antes de iniciar o programa de destino real.

O único problema é que você precisa rootcriar o novo espaço para nome ...

TomH
fonte
Eu pensei sobre isso. Mas isso é possível sem ser raiz? Existe um script / programa pronto para isso disponível?
Joachim Breitner
1
Sim, parece que você precisa ser root, pelo menos com o kernel 3.7.
TomH
Eu estava olhando mais para esta solução. É possível ligar / montar recursivamente a um / novo, mas não e marcá-lo recursivamente como somente leitura.
Joachim Breitner
2

Você poderia executá-lo em um chroot, montando versões especiais /tmp e assim por dentro. Talvez o systemd seja útil, e particularmente o systemd-nspawn (1) , que se parece exatamente com o que você deseja.

vonbrand
fonte
2

Uma máquina virtual possibilitaria que o script escrevesse em qualquer lugar sem afetar o sistema host e inspecionasse para onde ele realmente estava tentando gravar, o que parece ser o objetivo.

Por exemplo, você pode iniciar facilmente o Arch Linux com

kvm -boot d -m 512 -cdrom archlinux-*.iso
l0b0
fonte
1
Ainda quero executar o programa na máquina atual para evitar a necessidade de configurar um novo sistema, novo ambiente etc. Uma máquina virtual é muito pesada para o meu caso de uso.
Joachim Breitner
2

Fazer algumas configurações iniciais como root é realmente a maneira mais fácil. Especificamente, um chroot em uma montagem de ligação somente leitura é o caminho de menor resistência.

Você pode usar bindfs em vez de mount --bindcriar a visualização somente leitura sem precisar ser raiz. No entanto, você precisa fazer algo como root para impedir o acesso a outros arquivos, como o chroot.

Outra abordagem é LD_PRELOADuma biblioteca que se conecta à abertura de arquivos e se recusa a permitir a gravação. Isso não requer privilégios especiais. Do ponto de vista da segurança, isso pode ser ignorado, mas não há problema em seu caso de uso em que você só precisa conter um recurso específico e não um código nativo arbitrário. Eu não conheço uma biblioteca existente para isso, no entanto. LD_PRELOADtambém pode ser usado para limitar o programa à exibição somente leitura criada com mount --bindou bindfs; novamente, não conheço uma biblioteca existente.

No Debian e derivados, você pode configurar um ambiente schroot . O Schroot é raiz setuid e precisa ser configurado como raiz, mas pode ser executado por qualquer usuário autorizado.

Um método que não requer nenhuma cooperação da raiz é executar o processo em uma máquina virtual. Você pode configurar o KVM ou o VirtualBox ou o Linux no modo de usuário . É um pouco pesado e significa consumo de memória extra, mas não deve afetar significativamente a velocidade da computação simbólica bruta.

Como "encarcerar" um processo sem ser root? pode fornecer alguma inspiração.

Gilles 'SO- parar de ser mau'
fonte
1

Uma maneira de pelo menos impedir que o processo grave os arquivos (mas não os crie) é ligar ulimit -f 0primeiro. Isso interromperá o processo assim que ele tentar gravar em um arquivo, mas a criação de arquivos vazios ainda é possível.

Joachim Breitner
fonte