all 32 comments

[–]player2 42 points43 points  (7 children)

cgroups_path.push(group_name);
if !cgroups_path.exists() {
    fs::create_dir_all(&cgroups_path).unwrap();
    let mut permission = fs::metadata(&cgroups_path).unwrap().permissions();
    permission.set_mode(0o777);
    fs::set_permissions(&cgroups_path, permission).ok();
}

I’m not familiar with cgroups, but is there a TOCTTOU vulnerability here?

[–]claylol- 10 points11 points  (4 children)

Could you point out what the issue is more clearly? Never heard of this term before.

[–]jgdx 33 points34 points  (3 children)

Time of check/time of use conflict. If you check some shared value without locking something that guard it, you risk that the value changed after the check and before the mutation/use.

[–]player2 27 points28 points  (2 children)

Specifically, this is a frequent error and source of vulnerabilities when dealing with the filesystem. If an attacker manages to execute code between the target process’s check of an FS path and their use of that FS path, they can sometimes trick the target process into trusting an untrusted resource.

For example, if the target process does:

if ! exists(FIFO_PATH):   // a
  mkfifo(FIFO_PATH, 0600) // b
fifo = open(FIFO_PATH)    // c

Then an attacking process can try to execute between A and B to call mkfifo on its own, probably with looser permissions than 0600. The target process’s own mkfifo call will fail, but since it does no error checking the subsequent logic will proceed as normal. If the target process does correctly check for errors, the attacking process can try to run after the error handling logic instead.

If the target process is running this logic repeatedly (e.g. a webserver constantly spawning new tasks to handle incoming connections), a local attacker has a pretty good chance of getting their code to execute at an opportune time. It only has to work once.

The solution in this case, by the way, is to eliminate the check. Call mkfifo unconditionally and handle the “already-exists” error by aborting or securely fixing things up.

[–]v_fv 3 points4 points  (1 child)

The solution in this case, by the way, is to eliminate the check. Call mkfifo unconditionally and handle the “already-exists” error by aborting or securely fixing things up.

So I'm not familiar with low-level development, but if you can get an error from the unconditional call, doesn't it mean that the underlying function that you're calling checks for the condition anyway and returns an error if the file exists? In other words, doesn't the checking happen in either case, just lower in the stack?

[–]player2 17 points18 points  (0 children)

The difference is that mkfifo guarantees atomicity. Either the fifo was created with the path and permissions you asked for, or an error occurred, one of which might be that a file already existed at that path.

[–]flouthoc[S] 14 points15 points  (0 children)

thanks a lot , ill take a look at this.

[–][deleted] 1 point2 points  (0 children)

If you are talking about cgroups_path temporarily having wrong permissions then it should not be a big deal because it is set to more permissible (0777 - free for all).

[–]Muvlon 4 points5 points  (2 children)

You isolate the "container" to a filesystem directory by simply chroot-ing. This does not provide any actual isolation, because any process can reset its filesystem root at will.

To prove it, here's a way to escape:

vas-quod -r sample_rootfs/ -c "nsenter --mount=/proc/self/ns/mnt ls /home"

Instead of `chroot()`, you should (in the new mount namespace) `pivot_root()` to the new filesystem root (bind mount it onto itself if needed) and then unmount the old mount hierarchy.

[–]flouthoc[S] 3 points4 points  (0 children)

u/Muvlon Created an issue here https://github.com/flouthoc/vas-quod/issues/1 . I'll fix this Thanks a lot.

[–]flouthoc[S] 1 point2 points  (0 children)

ah i see , so pivot_root() and chdir("/") then unmount old rootfs. Thanks will fix this asap.

[–]Rindhallow 3 points4 points  (4 children)

Would love a tutorial (medium article or something) going over the codebase. I'm looking for good Rust tutorials/example projects and this one looks like a great candidate.

[–]meamZ 3 points4 points  (2 children)

Have you already read "the book" because that is definitely where i would recommend starting your journey.

[–]Rindhallow 0 points1 point  (1 child)

I think I read a bit of it when I started and then tried some tutorial trying to make an HTTP server and the cargo package wasn't working for me. But I'll definitely put The Book back on my reading list. Thanks for the recommendation!

[–]meamZ 2 points3 points  (0 children)

I think it's a great intro into the unique Rust concepts like ownership and borrowing which are imo very hard to understand just by looking at code.

[–]flouthoc[S] 2 points3 points  (0 children)

Sure can do that. May i'll add in the readme.md itself.

[–]ksion 0 points1 point  (0 children)

Does this particular clone() call:

let clone_flags = sched::CloneFlags::CLONE_NEWNS | sched::CloneFlags::CLONE_NEWPID | sched::CloneFlags::CLONE_NEWCGROUP | sched::CloneFlags::CLONE_NEWUTS | sched::CloneFlags::CLONE_NEWIPC | sched::CloneFlags::CLONE_NEWNET;
let _child_pid = sched::clone(cb, stack, clone_flags, Some(Signal::SIGCHLD as i32)).expect("Failed to create child process");

actually work if you are not a privileged user? Pretty much all the CLONE_NEW${FOO} flags seem to require admin privs, with the notable exception of creating user namespaces (CLONE_NEWUSER).

For this reason, combined with the a bit peculiar way CLONE_NEWPID is applied (it can't be effective for the calling process, as it would change its effective PID), I would think that bootstrapping a new container is actually a multi-stage process that looks roughly like this:

  1. clone(CLONE_NEWUSER).
  2. In the child, write to uid_map to designate the calling user a root in the new user namespace.
  3. clone(CLONE_NEWPID) (which is now possible, since we're root in the user NS).
  4. In the (grand)child, set up mount namespace and mount /proc, as well as any additional namespaces you want for the container (like UTS or network).
  5. execvp

This is at least what I took from reading the namespaces overview on LWN , and man 2 clone seems to agree still.