Skip to content

Commit

Permalink
cli: add new "--limit-<XXX>" options
Browse files Browse the repository at this point in the history
To give the user more control over the bst process, add three new options for
runing `setrlimit` before exec.

    --limit-core: RLIMIT_CORE
    --limit-nofile: RLIMIT_NOFILE
    --limit-nproc: RLIMIT_NPROC

Each option requires an argument which is parsed as a colon-separated tuple of
hard, soft resource limits.  Each of hard and soft limits are optional, and
when one is not specified, it will use the current value.  The only exception
is for the special value ":" which will use the current hard limit, and set
the soft limit to the hard limit value.

    --limit-nofile=100 => setrlimit(RLIMIT_NOFILE, (100, 100))
    --limit-nofile=100:50 => setrlimit(RLIMIT_NOFILE, (100, 50))
    --limit-nofile=100: => setrlimit(RLIMIT_NOFILE, (100, cur))
    --limit-nofile=:100 => setrlimit(RLIMIT_NOFILE, (cur, 100))
    --limit-nofile=: => setrlimit(RLIMIT_NOFILE, (cur, hard))

Added test cases to bst.t and also added a unit test for the parsing
code.

Usage strings:
    --limit-core (<value>)|(<hard>:<soft>)
                            Set RLIMIT_CORE to the provided value(s).
    --limit-nofile (<value>)|(<hard>:<soft>)
                            Set RLIMIT_NOFILE to the provided value(s).
    --limit-nproc (<value>)|(<hard>:<soft>)
                            Set RLIMIT_NPROC to the provided value(s).
  • Loading branch information
wade-arista authored and Snaipe committed Aug 31, 2020
1 parent c01b008 commit c0b0c4c
Show file tree
Hide file tree
Showing 12 changed files with 449 additions and 37 deletions.
14 changes: 10 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ SRCS := \
cp.c \
enter.c \
kvlist.c \
bst_limits.c \
main.c \
mount.c \
net.c \
Expand All @@ -29,6 +30,12 @@ SRCS := \
OBJS := $(subst .c,.o,$(SRCS))
BINS := bst bst-unpersist bst-init

all: $(BINS) man

TEST_BINS := test/print_limits test/test_limit_parsing

test/test_limit_parsing: bst_limits.o

ifeq ($(shell id -u),0)
SUDO =
else
Expand All @@ -45,8 +52,6 @@ CHOWN = :
CHMOD = :
endif

all: $(BINS) man

generate: usage.txt
(echo "/* Copyright © 2020 Arista Networks, Inc. All rights reserved."; \
echo " *"; \
Expand Down Expand Up @@ -89,10 +94,11 @@ install: $(BINS) man
|| ($(CHOWN) root $(BST_INSTALLPATH)-unpersist && $(CHMOD) u+s $(BST_INSTALLPATH)-unpersist)

check: export PATH := $(DESTDIR)$(BINDIR):${PATH}
check: $(BINS)
check: $(BINS) $(TEST_BINS)
./test/cram.sh test
./test/test_limit_parsing

clean:
$(RM) $(BINS) $(OBJS) bst.1.gz
$(RM) $(BINS) $(TEST_BINS) $(OBJS) bst.1.gz

.PHONY: all clean install generate check man
31 changes: 27 additions & 4 deletions bst.1.scd
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ Users of bst may choose to opt-out of some of the isolation.

All namespaces are unshared by default; _e.g.,_ if _--share-mnt_
is not given, then the child process runs in a new (unshared)
mount namespace intialized as a copy of bst's parent's mount
mount namespace initialized as a copy of bst's parent's mount
namespace.

\--persist <dir>
Expand Down Expand Up @@ -140,7 +140,7 @@ Users of bst may choose to opt-out of some of the isolation.
executable does not need to exist in the root specified by _--root_).

If an empty *<argv>* is passed to _--init_, no init process will be
spawed by bst, and *<executable>* will be executed directly.
spawned by bst, and *<executable>* will be executed directly.

If bst unshares the pid namespace and no _--init_ is specified, it uses
by default *bst-init*(1).
Expand All @@ -165,7 +165,7 @@ Users of bst may choose to opt-out of some of the isolation.
\--gid-map <inner:outer:length>[,<inner:outer:length>...]
Override the default generated [ug]id map. The map must be fully-specified.

By default, *bst* maps your current [UG]ID to 0, then maps continously
By default, *bst* maps your current [UG]ID to 0, then maps continuously
all allotted sub[ug]ids for the current [UG]ID as written in
_/etc/sub[ug]id_ (see *subuid*(5), *subgid*(5)).

Expand All @@ -179,6 +179,29 @@ Users of bst may choose to opt-out of some of the isolation.
Supported interface types and valid options are described in more detail
in the *NETWORKING* section.

\--limit-<limit> (<value>|[<hard>][:[<soft>]])
Set the specified hard and soft resource limits.

When either limit is not provided (empty string), use the
current value without trying to make any changes. When
neither limit is provided, this is a special case to set the
soft resource limit to the current hard limit value.

Without specifying a colon, simply set both hard and soft
limits to the provided `value'.

Available options:++
\--limit-core maximum size of core files created++
\--limit-nofile maximum number of open file descriptors++
\--limit-nproc the maximum number of user processes

Examples:++
\--limit-nproc 100 hard=100, soft=100++
\--limit-nproc 200:100 hard=200, soft=100++
\--limit-nproc :100 hard=(unchanged), soft=100++
\--limit-nproc 100: hard=100, soft=(unchanged)++
\--limit-nproc : hard=(unchanged), soft=(hard limit)

\--no-fake-devtmpfs
Do not replace devtmpfs mounts with a fake devtmpfs.

Expand Down Expand Up @@ -221,7 +244,7 @@ has been forked, but before any other initialization occurs.
In particular, this means that the setup script will run before any mounts
occurs, and before *bst* pivots/chroots into filesystem root.

Because the setup process is run very early, any preparative work done by
Because the setup process is run very early, any preparatory work done by
the process may be altered or undone by other command-line switches passed to
*bst* if not careful.

Expand Down
118 changes: 118 additions & 0 deletions bst_limits.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
#include <assert.h>
#include <err.h>
#include <errno.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <sys/resource.h>

/* parse_complete_rlim_t: parses an rlim_t value from `value_str`. Return 0 on
success, 1 otherwise. */
static int parse_complete_rlim_t(int resource, rlim_t *value, char const *value_str, rlim_t const *cur_value) {
assert(value_str);

/* an empty string means to use the current value */
if (*value_str == '\0') {
*value = *cur_value;
return 0;
}

char *endptr;
intmax_t parse_val = strtoimax(value_str, &endptr, 0);
if (parse_val == INTMAX_MIN || parse_val == INTMAX_MAX) {
return 1;
}

if (parse_val < 0) {
errno = EINVAL;
return 1;
}

/* This indicates a parsing error - since value_string is non-empty, the end
of the value is not the end of the string, so the value is not entirely
numeric. */
if (*endptr != '\0') {
errno = EINVAL;
return 1;
}

/* Before casting to rlim_t, check against the max possible rlim_t
value so that the (rlim_t) cast is guaranteed correct. */
if ((uintmax_t)parse_val >= RLIM_INFINITY) {
errno = EOVERFLOW;
return 1;
}

*value = (rlim_t)(parse_val);
return 0;
}

/* parse_rlimit: parses a tuple using a ':' separator charcter as (rlim_max,
[rlim_cur]). If the optional rlim_cur is not specified, use the rlim_max
value. Return 0 on success, 1 otherwise.
Example:
100 => (100, 100)
100:30 => (100, 30)
:30 => ("preserve current hard limit", 30)
100: => (100, "preserve current soft limit")
: => ("preserve current hard limit", "use current hard limit as soft limit")
limit and arg must be non-NULL. */
int parse_rlimit(int resource, struct rlimit *limit, char *arg)
{
assert(limit);
assert(arg);

if (arg[0] == '\0') {
errno = EINVAL;
return 1;
}

char const *hard_limit = arg;
char *p;
bool found_sep = false;
for (p = arg; *p != '\0'; ++p) {
if (*p == ':') {
*p = '\0';
found_sep = true;
++p;
break;
}
}
/* Special case: no ":" separator, so set hard and soft to <value> */
char const *soft_limit = found_sep ? p : hard_limit;

struct rlimit cur_limit_value;
rlim_t const *cur_hard_limit;
rlim_t const *cur_soft_limit;
if ( !soft_limit[0] || !hard_limit[0] ) {
if (getrlimit(resource, &cur_limit_value)) {
err(1, "getlrimit(%d) failed", resource);
return 1;
}
cur_hard_limit = &cur_limit_value.rlim_max;
cur_soft_limit = &cur_limit_value.rlim_cur;
}

if ( !soft_limit[0] && !hard_limit[0] ) {
/* special case: preserve hard limit, use that value as new soft
limit value */
limit->rlim_max = *cur_hard_limit;
limit->rlim_cur = *cur_hard_limit;
return 0;
}

if (parse_complete_rlim_t(resource, &limit->rlim_max, hard_limit, cur_hard_limit)) {
/* pass errno through to caller */
return 1;
}

if (parse_complete_rlim_t(resource, &limit->rlim_cur, soft_limit, cur_soft_limit)) {
/* pass errno through to caller */
return 1;
}

return 0;
}
3 changes: 3 additions & 0 deletions bst_limits.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#include <sys/resource.h>

int parse_rlimit(int resource, struct rlimit *limit, char *arg);
10 changes: 10 additions & 0 deletions enter.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <sys/mount.h>
#include <sys/personality.h>
#include <sys/prctl.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <syscall.h>
Expand Down Expand Up @@ -491,6 +492,15 @@ int enter(struct entry_settings *opts)
}
}

for (size_t resource = 0; resource < RLIM_NLIMITS; ++resource) {
struct rlimit const * value = opts->limits + resource;
if (value->rlim_max != RLIM_INFINITY) {
if (setrlimit(resource, value)) {
err(1, "setrlimit(%zu) failed", resource);
}
}
}

if (initfd != -1) {

if (!pid_unshare && prctl(PR_SET_CHILD_SUBREAPER, 1) == -1) {
Expand Down
5 changes: 4 additions & 1 deletion enter.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
# define ENTER_H_

# include <limits.h>
# include <time.h>
# include <sys/resource.h>
# include <sys/stat.h>
# include <time.h>
# include <unistd.h>
# include "mount.h"
# include "net.h"
Expand Down Expand Up @@ -63,6 +64,8 @@ struct entry_settings {

const char *arch;

struct rlimit limits[RLIM_NLIMITS];

const char *setup_program;
char *const *setup_argv;

Expand Down
32 changes: 31 additions & 1 deletion main.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,22 @@
* in the LICENSE file.
*/

#include <assert.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <getopt.h>
#include <inttypes.h>
#include <limits.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "enter.h"
#include "bst_limits.h"
#include "capable.h"
#include "enter.h"
#include "kvlist.h"

enum {
Expand All @@ -26,6 +29,9 @@ enum {
OPTION_GROUPS,
OPTION_WORKDIR,
OPTION_ARCH,
OPTION_LIMIT_CORE,
OPTION_LIMIT_NOFILE,
OPTION_LIMIT_NPROC,
OPTION_SHARE_CGROUP,
OPTION_SHARE_IPC,
OPTION_SHARE_MNT,
Expand Down Expand Up @@ -90,6 +96,12 @@ static void process_share_deprecated(struct entry_settings *opts, const char *op
}
}

static void handle_limit_arg(int resource, struct rlimit *opt_limits, char *arg, char const *which ) {
if (parse_rlimit(resource, opt_limits + resource, arg)) {
err(1, "error in --limit-%s value", which);
}
}

int usage(int error, char *argv0)
{
usage_txt[usage_txt_len - 1] = 0;
Expand All @@ -108,6 +120,11 @@ int main(int argc, char *argv[], char *envp[])
.gid = (gid_t) -1,
.umask = (mode_t) -1,
};
for (size_t resource = 0; resource < RLIM_NLIMITS; ++resource) {
struct rlimit *value = opts.limits + resource;
value->rlim_cur = RLIM_INFINITY;
value->rlim_max = RLIM_INFINITY;
}

static struct option options[] = {
{ "help", no_argument, NULL, 'h' },
Expand All @@ -120,6 +137,9 @@ int main(int argc, char *argv[], char *envp[])
{ "gid", required_argument, NULL, OPTION_GID },
{ "groups", required_argument, NULL, OPTION_GROUPS },
{ "arch", required_argument, NULL, OPTION_ARCH },
{ "limit-core", required_argument, NULL, OPTION_LIMIT_CORE },
{ "limit-nofile", required_argument, NULL, OPTION_LIMIT_NOFILE },
{ "limit-nproc", required_argument, NULL, OPTION_LIMIT_NPROC },
{ "share-cgroup", optional_argument, NULL, OPTION_SHARE_CGROUP },
{ "share-ipc", optional_argument, NULL, OPTION_SHARE_IPC },
{ "share-mnt", optional_argument, NULL, OPTION_SHARE_MNT },
Expand Down Expand Up @@ -246,6 +266,16 @@ int main(int argc, char *argv[], char *envp[])
opts.arch = optarg;
break;

case OPTION_LIMIT_CORE:
handle_limit_arg(RLIMIT_CORE, opts.limits, optarg, "core");
break;
case OPTION_LIMIT_NOFILE:
handle_limit_arg(RLIMIT_NOFILE, opts.limits, optarg, "nofile");
break;
case OPTION_LIMIT_NPROC:
handle_limit_arg(RLIMIT_NPROC, opts.limits, optarg, "nproc");
break;

case OPTION_SHARE_CGROUP:
case OPTION_SHARE_IPC:
case OPTION_SHARE_MNT:
Expand Down
30 changes: 30 additions & 0 deletions test/bst.t
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,33 @@ Testing hostname semantics
Testing persistence

$ mkdir -p foo bar; trap 'bst-unpersist foo && rmdir foo bar' EXIT; bst --persist=foo sh -c 'mount -t tmpfs none bar && echo hello > bar/greeting' && [ ! -f bar/greeting ] && bst --share-mnt=foo/mnt --share-user=foo/user sh -c '[ "$(cat '"$PWD"'/bar/greeting)" = "hello" ]'

Testing --limit-core / general tests
$ bst --limit-core=0 test/print_limits core
core: hard=0 soft=0

$ bst --limit-core=-1
bst: error in --limit-core value: Invalid argument
[1]

$ bst --limit-core=0:-1
bst: error in --limit-core value: Invalid argument
[1]

$ bst --limit-core=0xffffffffffffffffffffffffe
bst: error in --limit-core value: Numerical result out of range
[1]

Testing --limit-nofile
$ bst --limit-nofile=750 test/print_limits nofile
nofile: hard=750 soft=750

$ bst --limit-nofile=750:740 test/print_limits nofile
nofile: hard=750 soft=740

Testing --limit-nproc
$ bst --limit-nproc=3500 test/print_limits nproc
nproc: hard=3500 soft=3500

$ bst --limit-nproc=3500:3499 test/print_limits nproc
nproc: hard=3500 soft=3499
Loading

0 comments on commit c0b0c4c

Please sign in to comment.