MatrixAI / js-encryptedfs

Encrypted Filesystem for TypeScript/JavaScript Applications
https://polykey.com
Apache License 2.0
10 stars 3 forks source link

Chroot Lifecycle #61

Closed CMCDragonkai closed 2 years ago

CMCDragonkai commented 2 years ago

Description

The chroot lifecycle is now independent of other chroots and the root instance.

If the root instance stops, all chroot instances are stopped and also deleted from the chroot array.

If a chrooted instance restarts, it will readd itself to the chroot array.

Chroot instances have noop destruction, their state is not connected to the root instance.

Chroot instances are expected to be stopped after they are no longer of use.

The permission check for the execute permission is removed as this was not necessary according to may prototypes in C. In C, the chroot call is a privileged call and is only executable by root user. In our case, if the UID is made to be a different one, chroot is still possible, but execute permissions would be checked by the path navigation.

The result is that you use chroot only to change the root of your path manipulation, nothing else. They do not represent virtualisation, or hierarchical filesystems.

Related to https://github.com/MatrixAI/js-polykey/pull/266

Tasks

  1. [x] Update the start/stop lifecycle of chrooted EFS to not affect other chrooted EFS and not the root EFS
  2. [x] Add additional tests for chroot lifecycle, including start/stop of chrooted instances, the root instance, and destruction

Final checklist

CMCDragonkai commented 2 years ago

Example C code for prototyping the chroot.

#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <dirent.h>
#include <sys/stat.h>

// extern int errno ;

int main () {

  // you ahve to be root to do it
  chdir("/home/cmcdragonkai/Projects/js-encryptedfs/tmp/anotherdir");
  if (chroot("/home/cmcdragonkai/Projects/js-encryptedfs/tmp/anotherdir") != 0) {
    fprintf(stderr, "Value of errno: %d\n", errno);
    perror("chroot");
    return 1;
  }

  DIR * dp;
  struct dirent *ep;
  dp = opendir("../../..");

  if (dp != NULL) {
    while (ep = readdir(dp)) {
      puts(ep->d_name);
    }
    closedir(dp);
  }

  struct stat st;
  stat(".", &st);

  printf((st.st_mode & S_IRUSR) ? "r" : "-");
  printf((st.st_mode & S_IWUSR) ? "w" : "-");
  printf((st.st_mode & S_IXUSR) ? "x" : "-");
  printf((st.st_mode & S_IRGRP) ? "r" : "-");
  printf((st.st_mode & S_IWGRP) ? "w" : "-");
  printf((st.st_mode & S_IXGRP) ? "x" : "-");
  printf((st.st_mode & S_IROTH) ? "r" : "-");
  printf((st.st_mode & S_IWOTH) ? "w" : "-");
  printf((st.st_mode & S_IXOTH) ? "x" : "-");
  printf("\n\n");

  return 0;

}
CMCDragonkai commented 2 years ago

The chroot call also enables the block parameter of ready to ensure that it is not running at the same time as root instance start and stop.

I forgot this only applies to the immediate parent, so it's not entirely robust against race conditions if the root instance calls stop. This is not a big problem since it's unlikely to happen, but it's something to fix for the future.

CMCDragonkai commented 2 years ago

Actually it might not be a problem because of the order of stop logic which tries to stop all chrooted instances FIRST before stopping the inode manager and DB.

That will be blocked by any ongoing calls to do chroot. Which would then add a new instance in to the root chroots set, and prototyping with set shows that you can iterate and manipulate the set structure at the same time.

const set = new Set();

set.add(1);
set.add(2);
set.add(3);

for (const x of set) {
  set.delete(x);
  if (x === 1) {
    set.add(0);
  }
  console.log(x);
}

console.log(set);