capsicum-rs icon indicating copy to clipboard operation
capsicum-rs copied to clipboard

Feature Request: Add support for directories

Open pwrdwnsys opened this issue 9 years ago • 4 comments

In https://github.com/myfreeweb/rusty-sandbox there is support for defining a Directory containing a path under which all associated operations can be permitted (e.g. allow read of everything under /zdata/files). This functionality would be a nice addition to this crate.

Use case: combine with CAP_CONNECT to create a file downloader which can only write to specific directories (thinking of things like freebsd-update here :smirk:), or only permit an Iron/Nickel web server to read data from one location.

pwrdwnsys avatar Aug 17 '16 21:08 pwrdwnsys

After a quick look at rusty-sandbox, I doubt add_directory will work on FreeBSD, but I could be wrong. I wholeheartedly agree with this request, but I'm not sure if the implementation of this should be in this crate or in the implementors. AFAIK to acomplish this, one could use the equivalent of the following c, or what boils down to 1) get a descriptor to a directory, 2) limit the directory, 3) enter capability mode 4) open any files with openat using the discriptor from 1 (regular open will not work). This could be a little complicated if this were to be implemented in this crate because we would need to account for any number of directories and choose which fd to use.

#include <sys/capsicum.h>
#include <stdlib.h>
#include <err.h>

int main() {
    int res, dir, fd;
    cap_rights_t rights;

    dir = open("/tmp", O_RDONLY);
    if(dir <= 0) {
        err(EXIT_FAILURE, "open");
    }

    // Will allow reading an writing to /tmp
    cap_rights_init(&rights, CAP_READ | CAP_WRITE);
    res = cap_rights_limit(dir, &rights);
    if(res < 0) {
        err(EXIT_FAILURE, "cap_rights_limit");
    }

    res = cap_enter();
    if(res < 0) {
        err(EXIT_FAILURE, "cap_enter");
    }

    fd = openat(dir, "/tmp/foo", O_RDWR);
    if(fd < 0) {
        err(EXIT_FAILURE, "openat");
    }
    if (write(fd, "cap", 3) < 0) {
        err(EXIT_FAILURE, "write");
    }
    exit(0);
}

Great thought. I think more discussion on this could be helpful.

dlrobertson avatar Aug 18 '16 03:08 dlrobertson

I looked at the list of FreeBSD components which have been updated to use Capsicum, and as an example, rwhod(8) now limits itself to a single directory. This is quite similar to the example you gave above.

    dirfd = open(".", O_RDONLY | O_DIRECTORY);
    if (dirfd < 0) {
        syslog(LOG_WARNING, "%s: %m", _PATH_RWHODIR);
        exit(1);
    }
    cap_rights_init(&rights, CAP_CREATE, CAP_FSTAT, CAP_FTRUNCATE,
        CAP_LOOKUP, CAP_SEEK, CAP_WRITE);
    if (cap_rights_limit(dirfd, &rights) < 0 && errno != ENOSYS) {
        syslog(LOG_WARNING, "cap_rights_limit: %m");
        exit(1);
    }
    if (cap_enter() < 0 && errno != ENOSYS) {
        syslog(LOG_ERR, "cap_enter: %m");
        exit(1);
    }

I have done some testing with rusty-sandbox, and the directory specification does indeed work on FreeBSD (below is a small adaptation of one of the tests). I ran the following on FreeBSD 11 RC1 this afternoon:

extern crate rusty_sandbox;

use std::net;
use std::fs::File;
use std::io::{Read, Write};

use rusty_sandbox::Sandbox;

fn main() {
    Sandbox::new()
        .add_directory("temp", "/tmp")
        .sandboxed_fork(|ctx, _| {
            let mut file = ctx.directory("temp").unwrap()
                .open_options().write(true).create(true)
                .open("hello_rusty_sandbox").unwrap();
            file.write_all(b"Hello World").unwrap();
        }).unwrap().wait().unwrap();

    let mut buf = Vec::new();
    let mut f = File::open("/tmp/hello_rusty_sandbox").unwrap();
    f.read_to_end(&mut buf).unwrap();
    assert_eq!(&buf[..], b"Hello World");
}

I'll do some more investigation over the weekend.

pwrdwnsys avatar Aug 18 '16 14:08 pwrdwnsys

I am officially the worst maintainer on the face of the earth :smile: I finally got around to this (and hope to spend more time on this project) now.

I have done some testing with rusty-sandbox, and the directory specification does indeed work on FreeBSD (below is a small adaptation of one of the tests). I ran the following on FreeBSD 11 RC1 this afternoon:

Indeed, I did not look close enough at rusty-sandbox before I made my comment.

I implemented the Directory structure under capsicum::util::Directory this evening. It encapsulates what is discussed here. See tests/lib.rs:136-151 for details. This is only the absolute basics. open_file has no logic, and can only take what you would provide to openat. For example, if you gave lookup and read capabilities to /tmp you could not pass /tmp/foo as the path, but would have to pass foo or ./foo. I plan to continue working on this.

Closing the issue for now. If you have more suggestions, please let me know.

dlrobertson avatar Feb 28 '17 02:02 dlrobertson

That's great! I'll test it out!

pwrdwnsys avatar Feb 28 '17 02:02 pwrdwnsys

See https://github.com/dlrobertson/capsicum-rs/issues/3#issuecomment-282925365.

dlrobertson avatar Dec 02 '22 14:12 dlrobertson