Repositories / jai.git
jai.git
Clone (read-only): git clone http://git.guha-anderson.com/git/jai.git
@@ -1,45 +1,69 @@ #include "jai.h" -const std::string default_conf = - R"(# Note: instead of copying this file to create a custom configuration -# for certain commands, you can include this file by referernce with a -# line "conf default.conf" in your new configuration file. - -# The default mode is strict for all named sandboxes and casual for -# the default sandbox. A strict sandbox runs under the dedicated jai -# UID and starts with an empty home directory. A casual sandbox runs -# with your own UID and makes your home directory copy-on-write via an -# overlay mount. To change the default, you can uncomment one of the -# following: +const std::string jai_defaults = + R"(# This file contains generic defaults built into jai. It is intended +# to be included by other configuration files with a line: +# +# conf .defaults +# +# You can override settings in this file by editing it directly or by +# adding configuration directives to default.conf or other <name>.conf +# files after the `conf .defaults` line. (Later lines override +# previous ones in configuration files.) +# +# If you delete this file, jai will re-create it the next time it +# runs. You can also see the default contents of this file by running +# +# jai --print-defaults + + +# By default, jai stores private home directory state in $HOME/.jai +# (or in $JAI_CONFIG_DIR, if set). However, jai works best if the +# storage directory is not on NFS. If your home directory is on NFS, +# use a `storage` directive to specify a local storage location. + +# storage /some/local/directory + +# The default mode is strict for all named jails and casual for the +# default jail. A strict jail runs under the dedicated jai UID and +# starts with an empty home directory. A casual jail runs with your +# own UID and makes your home directory copy-on-write via an overlay +# mount. Strict mode cannot grant unrestricted access to directories +# on NFS file systems. You will have to use bare mode (which gives +# you a bare home directory, but still runs with your UID) to expose +# NFS directories. Uncomment any of the following to set the mode: # mode casual # mode bare # mode strict -# You can use use "name NAME" to specify different sandboxes. For -# casual sandboxes, the sandboxed home directory will be in -# /run/jai/$USER/NAME.home, and changed files will be in -# $HOME/.jai/NAME.changes. For strict sandboxes, the home directory -# will be $HOME/.jai/NAME.home. If you leave it undefined, the name -# will be "default" and the mode will default to casual, but if you -# define this to anything including "default", then the mode will be -# strict. +# You can use use "name NAME" to specify different jails. For casual +# jails, the home directory will be in /run/jai/$USER/NAME.home, and +# changed files will be in $HOME/.jai/NAME.changes. For strict jails, +# the home directory will be $HOME/.jai/NAME.home. If you leave name +# undefined, the name will be "default" and the mode will default to +# casual, but if you define this to anything including "default", then +# the default mode will be strict. # name default -# jai launches programs in a sandbox by running bash with the command -# name in "$0" and the arguments in "@". Altering command allow you -# to set enfironment variables or add command-line arguments. +# jai launches jailed programs by running bash with the command name +# in "$0" and the arguments in "@". Altering command allows you set +# environment variables dynamically or add command-line arguments, +# which is more useful to do in a non-default configuration file. -command "$0" "$@" +# command "$0" "$@" # Masked files are deleted when an overlayfs is first created, but # have no effect on existing overlays or on strict/bare jails. To # delete files from an existing overlay, delete them under # /run/jai/$USER/default.home. Otherwise, to apply new mask # directives after editing this file, you can run "jai -u" to unmount -# any existing overlays. +# any existing overlays. If you want to avoid masing any of these +# files in one particular configuration, you can use a directive such +# as `unmask .aws` to undo the effects from a previously included +# default file. mask .jai mask .ssh @@ -62,9 +86,11 @@ mask .config/BraveSoftware mask .bash_history mask .zsh_history -# The following environment variables will be removed from sandboxed +# The following environment variables will be removed from jail # environments. You can use * as a wildcard to match any variables -# matching the pattern. +# matching the pattern. If you want to undo any of these unsetenv +# commands in a particular config file, you can use setenv to reverse +# the effects of unsetenv. unsetenv AZURE_CLIENT_ID unsetenv AZURE_TENANT_ID @@ -97,3 +123,7 @@ unsetenv *_SOCKET unsetenv *_SOCKET_PATH unsetenv *_TOKEN )"; + +extern const std::string default_conf = + R"(conf .defaults +)";
@@ -341,15 +341,24 @@ open_flags_to_string(int flags) {O_TMPFILE, "O_TMPFILE"}, }); static constexpr auto known_flags = std::to_array<Flag>({ - {O_WRONLY, "O_WRONLY"}, {O_RDWR, "O_RDWR"}, - {O_CREAT, "O_CREAT"}, {O_EXCL, "O_EXCL"}, - {O_NOCTTY, "O_NOCTTY"}, {O_TRUNC, "O_TRUNC"}, - {O_APPEND, "O_APPEND"}, {O_NONBLOCK, "O_NONBLOCK"}, - {O_DSYNC, "O_DSYNC"}, {O_ASYNC, "O_ASYNC"}, - {O_DIRECT, "O_DIRECT"}, {O_LARGEFILE, "O_LARGEFILE"}, - {O_DIRECTORY, "O_DIRECTORY"}, {O_NOFOLLOW, "O_NOFOLLOW"}, - {O_NOATIME, "O_NOATIME"}, {O_CLOEXEC, "O_CLOEXEC"}, - {O_SYNC, "O_SYNC"}, {O_PATH, "O_PATH"}, + {O_WRONLY, "O_WRONLY"}, + {O_RDWR, "O_RDWR"}, + {O_CREAT, "O_CREAT"}, + {O_EXCL, "O_EXCL"}, + {O_NOCTTY, "O_NOCTTY"}, + {O_TRUNC, "O_TRUNC"}, + {O_APPEND, "O_APPEND"}, + {O_NONBLOCK, "O_NONBLOCK"}, + {O_DSYNC, "O_DSYNC"}, + {O_ASYNC, "O_ASYNC"}, + {O_DIRECT, "O_DIRECT"}, + {O_LARGEFILE, "O_LARGEFILE"}, + {O_DIRECTORY, "O_DIRECTORY"}, + {O_NOFOLLOW, "O_NOFOLLOW"}, + {O_NOATIME, "O_NOATIME"}, + {O_CLOEXEC, "O_CLOEXEC"}, + {O_SYNC, "O_SYNC"}, + {O_PATH, "O_PATH"}, }); std::string result; @@ -426,3 +435,36 @@ try_read_file(int dfd, path file) ret.append(buf, size_t(n)); } } + +Fd +ensure_file(int dfd, path file, std::string_view contents, int mode) +{ + assert(!file.empty()); + for (;;) { + if (Fd fd = openat(dfd, file.c_str(), O_RDONLY | O_CLOEXEC)) { + if (!S_ISREG(xfstat(*fd).st_mode)) + err("{}: not a regular file", fdpath(dfd, file)); + return fd; + } + if (errno != ENOENT) + syserr("{}", fdpath(dfd, file)); + + auto pp = file.parent_path(); + if (pp.empty()) + pp = "."; + Fd fd = xopenat(dfd, pp, O_TMPFILE | O_RDWR | O_CLOEXEC); + for (size_t i = 0; i < contents.size();) { + if (auto n = write(*fd, contents.data() + i, contents.size() - i); n < 0) + syserr(R"(write(O_TMPFILE for "{}"))", fdpath(dfd, file)); + else + i += n; + } + + if (linkat(*fd, "", dfd, file.c_str(), AT_EMPTY_PATH) == 0) + return fd; + if (errno != EEXIST) + syserr(R"(linkat(O_TMPFILE -> "{}"))", fdpath(dfd, file)); + if (!S_ISREG(xfstat(dfd, file).st_mode)) + err("{}: not a regular file", fdpath(dfd, file)); + } +}
@@ -239,11 +239,16 @@ xpipe() } inline struct stat -xfstat(int fd) +xfstat(int fd, path file = {}, FollowLinks follow = kFollow) { struct stat sb; - if (fstat(fd, &sb)) - syserr(R"(fstat("{}"))", fdpath(fd)); + if (file.empty()) { + if (fstat(fd, &sb)) + syserr(R"(fstat("{}"))", fdpath(fd)); + } + else if (fstatat(fd, file.c_str(), &sb, + follow == kFollow ? 0 : AT_SYMLINK_NOFOLLOW)) + syserr(R"({}stat("{}"))", follow == kFollow ? "" : "l", fdpath(fd, file)); return sb; } @@ -259,6 +264,8 @@ read_file(int dfd, path file = {}) throw res.error(); } +Fd ensure_file(int dfd, path file, std::string_view contents, int mode = 0600); + using ACL = RaiiHelper<acl_free, acl_t>; enum AclType {
@@ -242,6 +242,9 @@ environment before running the command. you must use this option if you have added new files to be masked, as masking only takes effect at the time an overlay home is created. +`--print-defaults` +: Prints the default contents for `$HOME/.jai/.defaults`. + `--version` : Prints the version number and copyright. @@ -277,12 +280,21 @@ setting the `JAI_CONFIG_DIR` environment variable. file for *cmd*, then *cmd*`.conf` is used. Otherwise `default.conf` is used. +`$HOME/.jai/.defaults` +: Reasonable system defaults to be included in `defaults.conf` or + *cmd*`.conf`. This file is created automatically by jai. The file + has no effect if you don't include it, but you should probably begin + all configuration files with the line `conf .defaults` to get the + defaults. + `$HOME/.jai/default.changes`, `$HOME/.jai/`*name*`.changes` : This "upper" directory is overlaid on your home directory and - contains changes that have been made inside a jail. If you make - changes in this directory, you may need to tear down and recreate - the sandboxed home directory with `jai -u`. The non-default version - is used when you specify `-n` *name* on the command line. + contains changes that have been made inside a casual jail. If you + make changes in this directory, you may need to tear down and + recreate the sandboxed home directory with `jai -u`. The + non-default version is used when you specify `-n` *name* on the + command line. If you specified `--storage=`*dir*, the changes + directory will be in *dir* instead of `$HOME/.jai`. `$HOME/.jai/default.work`, `$HOME/.jai/`*name*`.work` : This "work" directory is required by overlayfs, but does not contain @@ -290,15 +302,22 @@ setting the `JAI_CONFIG_DIR` environment variable. system may create files in here that you cannot delete. If you are trying to blow away an overlay directory to start from scratch and cannot delete this directory, try running `jay -u` which will clean - things up. + things up. If you specified `--storage=`*dir*, or used a symbolic + link for your changes directory, then the work directory will always + be next to the changes directory wherever that lives. + +`$HOME/.jai/default.home`, `$HOME/.jai/`*name*`.home` +: Private home directory for bare and strict jails. If you specified + `--storage=`*dir*, the these directories will be in *dir* instead. `/run/jai/$USER/default.home`, `/run/jai/$USER/`*name*`.home` -: Sandboxed home directories for jails. You can delete files with - sensitive data in these sandboxed directories to hide theme from - jailed processes, or see the `--mask` option. +: Home directories for casual jails. You can delete files with + sensitive data in these jail directories to hide theme from jailed + processes, or see the `--mask` option. `/run/jai/$USER/tmp/default`, `/run/jai/$USER/tmp/`*name* -: Private `/tmp` and `/var/tmp` directory made available in the jail. +: Private `/tmp` and `/var/tmp` directory (they are the same) in + jails. # BUGS
@@ -67,7 +67,6 @@ struct Config { void unmount(); void unmountall(); std::unique_ptr<Options> opt_parser(); - void make_default_conf(); bool parse_config_file(path file, Options *opts = nullptr); std::vector<const char *> make_env(); @@ -327,6 +326,8 @@ Config::make_blacklist(int dfd, path name) { Fd blacklistfd = ensure_dir(dfd, name.c_str(), 0700, kFollow); check_user(*blacklistfd); + if (is_mountpoint(*blacklistfd)) + err("{}: directory must not be a mountpoint", fdpath(*blacklistfd)); for (path p : mask_files_) { try { @@ -666,19 +667,6 @@ Config::unmountall() unlinkat(run_jai(), user_.c_str(), AT_REMOVEDIR); } -void -Config::make_default_conf() -{ - auto fd = xopenat(home_jai(), ".", O_RDWR | O_TMPFILE | O_CLOEXEC, 0600); - errno = EAGAIN; - if (write(*fd, default_conf.data(), default_conf.size()) != - default_conf.size()) - syserr("write(O_TMPFILE for default.conf)"); - if (linkat(*fd, "", home_jai(), "default.conf", AT_EMPTY_PATH) && - errno != EEXIST) - syserr("linkat({})", fdpath(home_jai(), "default.conf")); -} - extern "C" char **environ; std::vector<const char *> @@ -1009,12 +997,12 @@ The default is CMD.conf if it exists, otherwise default.conf)", (*opts)("--help", [] { usage(0); }); (*opts)("--version", version, "Print copyright and version then exit"); (*opts)( - "--print-default-conf", + "--print-defaults", [] { - write(1, default_conf.data(), default_conf.size()); + write(1, jai_defaults.data(), jai_defaults.size()); exit(0); }, - "Show contents of the default configuration file"); + "Show default contents of $JAI_CONFIG_DIR/.defaults"); option_help = opts->help(); std::vector<char *> cmd; @@ -1037,16 +1025,17 @@ The default is CMD.conf if it exists, otherwise default.conf)", return; } + ensure_file(conf.home_jai(), ".defaults", jai_defaults, 0600); + ensure_file(conf.home_jai(), "default.conf", jai_defaults, 0600); + if (!opt_C.empty()) { if (!conf.parse_config_file(opt_C)) err("{}: no such configuration file", opt_C.string()); } else if ((cmd.empty() || !conf.name_ok(cmd[0]) || !conf.parse_config_file(std::format("{}.conf", cmd[0]))) && - !conf.parse_config_file("default.conf")) { - conf.make_default_conf(); + !conf.parse_config_file("default.conf")) conf.parse_config_file("default.conf"); - } // Re-parse command line to override files opts->parse_argv(argc, argv);
@@ -27,4 +27,5 @@ xfork(std::uint64_t flags = 0) } \ } while (0) +extern const std::string jai_defaults; extern const std::string default_conf;