Search code examples
clinuxlinux-namespaces

How to provide file isolation using linux namespace


I am trying to run the same program in two linux namespace.

The program needs to read and write the file /tmp/server.log.

So I want to make sure that program A read/write server.log, but actually it reads and writes /tmp/server-A.log. And for program B read/write server.log, it is actually reading and writing /tmp/server-B.log.

I try to use mount but not succeeded ... Can anyone help me? Or is there another way for me to provide file isolation so that the two programs will not actually read/write the same file?

#define _GNU_SOURCE
#include<sched.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>

static int child_func(void* arg) {
  system("mount --bind /tmp ./a");
  FILE* file;
  file = fopen("/tmp/server.log","rw");
  // write some log ...
  return 0;
}

static int child2_func(void* arg) {
  system("mount --bind /tmp ./b");
  file = fopen("/tmp/server.log","rw");
  // write some log.... 
  return 0;
}


int main(int argc, char** argv) {
  // Allocate stack for child task.
  const int STACK_SIZE = 1 * 1024 * 1024;
  char* stack = malloc(STACK_SIZE);
  char* stack2 = malloc(STACK_SIZE);
  if (!stack || !stack2) {
    perror("malloc");
    exit(1);
  }
  pid_t pid,pid2;


  if ((pid = clone(child_func, stack + STACK_SIZE, CLONE_NEWPID | CLONE_NEWUTS | CLONE_NEWNS | CLONE_NEWNET | SIGCHLD, NULL)) == -1) {
    perror("clone");
    exit(1);
  }

  if ((pid2 = clone(child2_func, stack2 + STACK_SIZE, CLONE_NEWPID | CLONE_NEWUTS | CLONE_NEWNS | CLONE_NEWNET | SIGCHLD, NULL)) == -1) {
    perror("clone");
    exit(1);
  }


  waitpid(pid,NULL,0);
  waitpid(pid2,NULL,0);


  return 0;
}

Update: I solve the problem based on the solutions answered below! Their solutions really help me!


Solution

  • You want something like this:

    #define _GNU_SOURCE
    
    #include <sched.h>
    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/mount.h>
    #include <sys/wait.h>
    #include <unistd.h>
    
    int doChild(const char *source) {
        if(unshare(CLONE_NEWNS)) {
            perror("unshare");
            return 1;
        }
        if(mount("none", "/", NULL, MS_REC|MS_PRIVATE, NULL)) {
            perror("mount");
            return 1;
        }
        if(mount(source, "/tmp/server.log", NULL, MS_BIND, NULL)) {
            perror("mount");
            return 1;
        }
        execlp("myunmodifiablepythonscript", "myunmodifiablepythonscript", (char*)NULL);
        perror("execlp");
        return 1;
    }
    
    int main(void) {
        pid_t pidA, pidB;
        pidA = fork();
        if(pidA < 0) {
            perror("fork");
            return 1;
        } else if(pidA == 0) {
            return doChild("/tmp/server-A.log");
        }
        pidB = fork();
        if(pidB < 0) {
            perror("fork");
            /* n.b.: pidA will still be running as an orphan. */
            return 1;
        } else if(pidB == 0) {
            return doChild("/tmp/server-B.log");
        }
        waitpid(pidA, NULL, 0);
        /* n.b.: if pidB finishes first, it will be a zombie until pidA finishes. */
        waitpid(pidB, NULL, 0);
        return 0;
    }
    

    A few notes:

    • Using clone correctly (which you weren't) is a pain. It's much easier to just use fork and then unshare after.
    • systemd stupidly makes mounts shared by default, which basically makes mount namespaces do nothing (i.e., changes will propagate back to other namespaces, thus defeating the purpose of a private namespace). mount("none", "/", NULL, MS_REC|MS_PRIVATE, NULL) undoes that to make them actually work.
    • I'm not sure what you were trying to bind mount, but it was the wrong thing. The right thing is mounting the individual log to the shared name.