Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

I want to run a command on Linux in a way that it cannot create or open any files to write. It should still be able to read files as normal (so an empty chroot is not an option), and still be able to write to files already open (especially stdout).

Bonus points if writing files to certain directories (i.e. the current directory) is still possible.

I’m looking for a solution that is process-local, i.e. does not involve configuring things like AppArmor or SELinux for the whole system, nor root privileges. It may involve installing their kernel modules, though.

I was looking at capabilities and these would have been nice and easy, if there were a capability for creating files. ulimit is another approach that would be convenient, if it covered this use case.

share|improve this question
    
Too many programs assume that they are able to write files as a matter of course (and fail in strange ways when they can't). strace tells you what files the program is opening. Why do you want to do this? Is it a specific program, or do you want this for testing or something else? Can you run the program as a user/group which doesn't have permission to write almost everywhere except in the current directory? Modern Linux disttributions use the idea of a group for each user, so this should be relatively easy to set up. –  vonbrand Feb 13 '13 at 11:48
    
It is a special program (Isabelle) that interprets code in a somewhat safe way already (no arbitrary code execution), but still allows the code to create files in arbitrary places. As the code is untrusted, I want to prevent this from happening (by aborting the program). The program already runs as a special user, but I’d feel safer if the code could not clobber up, say, /tmp or similar places. –  Joachim Breitner Feb 13 '13 at 11:50

9 Answers 9

How about creating an empty chroot, then bind-mount the main filesystem as read-only inside the chroot?

Should probably be something like this to create a read-only bind-mount:

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

You can bind-mount other directories which you want the jail to have write access to as well. Be careful if you need to bind-mount special directories (/dev/, /proc/, /sys/), mounting them as-is may be insecure.

share|improve this answer
    
Again, needs root privileges and other ”global setup”. But an option, yes. –  Joachim Breitner Feb 13 '13 at 17:07

You could run it in a chroot, mounting special versions of /tmp and such inside. Perhaps systemd is of help, and particularly systemd-nspawn(1), which looks just like what you want.

share|improve this answer

The simplest solution is probably a wrapper program that creates a new filesystem namespace with the relevant filesystems mounted read-only and then execs the program you are trying to restrict.

This is what systemd does when you use ReadOnlyDirectories= to mark certain directories as read-only for a service. There is also an unshare command in util-linux that can do the work of creating a new namespace, so you can do something like:

unshare -m <wrapper>

where wrapper would then just have to remount filesystems as required before starting the real target program.

The only problem is that you need to be root to create the new namespace...

share|improve this answer
    
I thought about this. But is this possible without being root? Is there a ready made script/program for that available? –  Joachim Breitner Feb 13 '13 at 12:13
    
Yes, it seems that you do need to be root, at least with the 3.7 kernel. –  TomH Feb 13 '13 at 12:27
    
I was looking further at this solution. It is possible to recursively bind-mount / to a new /, but not and recursivley mark it as read-only. –  Joachim Breitner Feb 13 '13 at 19:28

A virtual machine would make it possible for the script to write anywhere without affecting the host system, and to inspect where it's actually trying to write to, which seem to be the goals.

For example, you can easily start up Arch Linux with

kvm -boot d -m 512 -cdrom archlinux-*.iso
share|improve this answer
    
I still want to run the program in the current machine to avoid having to set up a new system, new environment etc. A virtual machine is way too heavyweight for my use case. –  Joachim Breitner Feb 13 '13 at 12:33

Would you consider writing a substitute to open(…) function, and loading it using LD_PRELOAD?

share|improve this answer
1  
You probably mean open... Well, I would consider using an existing solution that uses this approach, yes. –  Joachim Breitner Feb 13 '13 at 12:18
1  
There is something like this at github.com/certik/restrict, but it is configured by compiling and doesn’t seem to be in widespread use. –  Joachim Breitner Feb 13 '13 at 12:27
    
Yes, sorry, my mistake, updating the answer… But it seems to me you will have to substitute one for the write(…) as well. –  Leonid Feb 13 '13 at 13:34
    
As for the github.com/certik/restrict, yes, you're completely right. –  Leonid Feb 13 '13 at 13:37
up vote 2 down vote accepted

It seems that the right tool for this job is seccomp. Based on fsync-ignoring code by Bastian Blank, I came up with this relatively small file that causes all its childs to not be able to open a file for writing:

/*
 * Copyright (C) 2013 Joachim Breitner <mail@joachim-breitner.de>
 *
 * Based on code Copyright (C) 2013 Bastian Blank <waldi@debian.org>
 *
 * 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;
}

Here you can see that it is still possible to read files:

[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
/*

It does not prevent deleting files, or moving them, or other file operations besides opening, but that could be added.

A tool that enables this without having to write c code is syscall_limiter.

share|improve this answer
2  
Note that secure approach is to whitelist syscalls, not to blacklist them. If too much is denied, external unsanboxed helpers can be used to assist the program. With LD_PRELOAD such helpers can be made transparent to the program we running. –  Vi. Mar 6 '13 at 18:08

Doing some initial setup as root is really the easiest way. Specifically, a chroot into a read-only bind mount is the path of least resistance.

You can use bindfs instead of mount --bind to create the read-only view without needing to be root. However, you do need to do something as root to prevent access to other files, such as chroot.

Another approach is to LD_PRELOAD a library that hooks into file opening and refuses to allow writing. This requires no special privileges. From a security perspective, this can be bypassed, but it's ok for your use case where you only need to contain a specific feature and not arbitrary native code. I don't know of an existing library for this, however. LD_PRELOAD could also be used to confine the program to the read-only view created with mount --bind or bindfs; again, I don't know of an existing library.

On Debian and derivatives, you can set up a schroot environment. Schroot is setuid root and needs to be configured as root, but can be executed by any authorized user.

A method that doesn't require any cooperation from root is to run the process in a virtual machine. You could set up KVM or VirtualBox, or user-mode Linux. It's a bit heavyweight, and will mean extra memory consumption, but shouldn't affect the speed of raw symbolic computation significantly.

How to "jail" a process without being root? might provide some inspiration.

share|improve this answer

One way to at least prevent the process from writing the files (but not from creating them) is to call ulimit -f 0 first. This will abort the process as soon as it tries to write to a file, but creating empty files is still possible.

share|improve this answer

You can simple use use fcntl to acquire lock on the file.

Reference: http://www.thegeekstuff.com/2012/04/linux-file-locking-types/

share|improve this answer
    
This doesn't relate to the question at all. –  Gilles Feb 13 '13 at 23:01

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.