c++linux-kernelforklinux-namespaces

unshare user namespace, fork, map uid then execvp failing


I am trying to do the following sequence of actions:

However, when running id, my code outputs the user as a nobody or fails without error.

#include <sched.h>
#include <cstdio>
#include <cstring>
#include <cerrno>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/mount.h>
#include <system_error>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>

void unshare_user_namespace() {
  if (0 != unshare(CLONE_NEWUSER)) {
    fprintf(stderr, "%s\n", "USER unshare has failed");
    exit(1);
  }
}

void map_id() {
  int pid = getpid();
  char file[100];
  if (0 > sprintf(file, "/proc/%d/uid_map", pid)) {
    printf("Couldn't sprintf uid_map path.");
    exit(1);
  }

  int fd;
  fd = open(file, 1);
  if (fd < 0) {
    printf("Coudln't open file for writing.\n");
    exit(1);
  }

  int uid = getuid();
  char * buf;
  if (0 > sprintf(buf, "0 %d 1", uid)) {
    printf("Couldn't sprintf uid_map content.");
    exit(1);
  }

  if (write(fd, buf, strlen(buf))) {
    printf("Coudln't write mapping into file.\n");
    exit(1);
  }

  free(buf);
  close(fd);
}

void start(char * command, char ** args) {
  unshare_user_namespace();
  int fork_pid = fork();

  if (-1 == fork_pid) {
    fprintf(stderr, "%s\n", "couldn't fork");
    exit(1);
  }

  if (0 == fork_pid) {
    map_id();

    if (-1 == execvp(command, args)) {
      fprintf(stderr, "%s\n", "couldn't execvp");
      exit(1);
    }
  }
}

int main(int argc, char ** argv) {
  start(argv[1], & argv[1]);
  int status;
  wait( & status);
  return 0;
}

I tried reading the man pages for namespaces, unshare etc but couldn't figure out what's wrong with my code.

To run the code:

$ g++ <file_containing_code> && ./a.out id

Solution

  • Pretty sure you've already found the answer, but this is a minimal sample I could come up with:

    // gcc -Wall -std=c11
    #define _GNU_SOURCE
    #include <stdio.h>
    #include <unistd.h>
    #include <sched.h>
    #include <sys/wait.h>
    #include <stdlib.h>
    #include <stdarg.h>
    
    void write_to_file(const char *which, const char *format, ...) {
      FILE * fu = fopen(which, "w");
      va_list args;
      va_start(args, format);
      if (vfprintf(fu, format, args) < 0) {
        perror("cannot write");
        exit(1);
      }
      fclose(fu);
    }
    
    int main(int argc, char ** argv) {
      // array of strings, terminated with NULL entry
      char **cmd_and_args = (char**) calloc(argc, sizeof(char*));
      for (int i = 1 ; i < argc; i++) {
        cmd_and_args[i-1] = argv[i];
      }
      uid_t uid = getuid();
      gid_t gid = getgid();
      // first unshare
      if (0 != unshare(CLONE_NEWUSER)) {
        fprintf(stderr, "%s\n", "USER unshare has failed");
        exit(1);
      }
      // remap uid
      write_to_file("/proc/self/uid_map", "0 %d 1", uid);
      // deny setgroups (see user_namespaces(7))
      write_to_file("/proc/self/setgroups", "deny");
      // remap gid
      write_to_file("/proc/self/gid_map", "0 %d 1", gid);
      // exec the command
      if (execvp(cmd_and_args[0], cmd_and_args) < 0) {
        perror("cannot execvp");
        exit(1);
      }
      // unreachable
      free(cmd_and_args);
      return 0;
    }