From b68c5c12912680a64811326ca965e474f8157100 Mon Sep 17 00:00:00 2001 From: Joachim Nilsson Date: Sun, 26 Nov 2017 16:35:37 +0100 Subject: [PATCH] Fix #45: add support for rlimit per service/run/task/inetd/tty This patch extends the existing rlimit implementation to support setting limits per service, run/task, inetd, and tty. Use rlimit in /etc/finit.conf to change the global setting, which is then inherited to each /etc/finit.d/*.conf. For each .conf file the rlimit is reinitialized to the global finit.conf settings. Also, add `unlimited` keyword, to replace the now deprecated `infinity` keyword. The latter is however kept, for compatibility with previous releases, for the foreseeable future. Signed-off-by: Joachim Nilsson --- doc/config.md | 11 ++-- plugins/dbus.c | 3 +- src/conf.c | 136 +++++++++++++++++++++++++++++++------------------ src/conf.h | 5 ++ src/exec.c | 16 ++++-- src/finit.c | 2 +- src/helpers.h | 5 +- src/service.c | 26 +++++++--- src/service.h | 2 +- src/svc.h | 4 ++ src/tty.c | 14 +++-- src/tty.h | 6 ++- 12 files changed, 154 insertions(+), 76 deletions(-) diff --git a/doc/config.md b/doc/config.md index d471e7ea..56a89ae7 100644 --- a/doc/config.md +++ b/doc/config.md @@ -51,12 +51,15 @@ Syntax * `network ` Script or program to bring up networking, with optional arguments -* `rlimit RESOURCE ` +* `rlimit RESOURCE ` Set the hard or soft limit for a resource. `RESOURCE` is a lower-case string matching the `RLIMIT_` constants from `setrlimit(2)`, without - the prefix. E.g. to set `RLIMIT_CPU`, use `cpu`. The limit is an - integer that depends on the resource being modified, see the man page - for more information. The special limit `infinity` means unlimited. + the prefix. E.g. to set `RLIMIT_CPU`, use `cpu`. + + The limit is an integer that depends on the resource being modified, + see the man page, or the kernel `/proc/PID/limits` file, for more + information. Finit versions before v3.1 used `infinity` for + `unlimited`, which is still a supported keyword. ```shell # No process is allowed more than 8MB of address space diff --git a/plugins/dbus.c b/plugins/dbus.c index 368f4722..5b938ea1 100644 --- a/plugins/dbus.c +++ b/plugins/dbus.c @@ -29,6 +29,7 @@ #include "helpers.h" #include "plugin.h" #include "service.h" +#include "conf.h" #define DAEMON "dbus-daemon" #define ARGS "--nofork --system" @@ -58,7 +59,7 @@ static void setup(void *arg) /* Register service with Finit */ snprintf(line, sizeof(line), "[S12345] %s %s -- %s", cmd, ARGS, DESC); - if (service_register(SVC_TYPE_SERVICE, line, NULL)) + if (service_register(SVC_TYPE_SERVICE, line, global_rlimit, NULL)) _pe("Failed registering %s", DAEMON); free(cmd); diff --git a/src/conf.c b/src/conf.c index d4a45d11..946c1440 100644 --- a/src/conf.c +++ b/src/conf.c @@ -38,6 +38,7 @@ #define MATCH_CMD(l, c, x) \ (!strncasecmp(l, c, strlen(c)) && (x = (l) + strlen(c))) +struct rlimit global_rlimit[RLIMIT_NLIMITS]; static int parse_conf(char *file); @@ -163,59 +164,74 @@ static const struct rlimit_name rlimit_names[] = { { NULL, 0 } }; -void conf_parse_rlimit(char *line) +int str2rlim(char *str) { - struct rlimit rlim; - rlim_t cfg, *set; - const struct rlimit_name *name; + const struct rlimit_name *rn; + + for (rn = rlimit_names; rn->name; rn++) { + if (!strcmp(str, rn->name)) + return rn->val; + } + + return -1; +} + +char *rlim2str(int rlim) +{ + const struct rlimit_name *rn; + + for (rn = rlimit_names; rn->name; rn++) { + if (rn->val == rlim) + return rn->name; + } + + return "unknown"; +} + +void conf_parse_rlimit(char *line, struct rlimit arr[]) +{ + char *level, *limit, *val; int resource = -1; + rlim_t cfg, *set; - char *tok = strtok(line, " \t"); + level = strtok(line, " \t"); + if (!level) + goto error; - if (tok && !strcmp(tok, "soft")) - set = &rlim.rlim_cur; - else if (tok && !strcmp(tok, "hard")) - set = &rlim.rlim_max; + limit = strtok(NULL, " \t"); + if (!limit) + goto error; + + resource = str2rlim(limit); + if (resource < 0 || resource > RLIMIT_NLIMITS) + goto error; + + val = strtok(NULL, " \t"); + if (!val) + goto error; + + if (!strcmp(level, "soft")) + set = &arr[resource].rlim_cur; + else if (!strcmp(level, "hard")) + set = &arr[resource].rlim_max; else goto error; - tok = strtok(NULL, " \t"); - if (!tok) - goto error; - - for (name = rlimit_names; name->name; name++) { - if (!strcmp(tok, name->name)) - resource = name->val; - } - - if (resource < 0) - goto fail; - - tok = strtok(NULL, " \t"); - if (!tok) - goto fail; - - if (!strcmp(tok, "infinity")) { + /* Official keyword from v3.1 is `unlimited`, from prlimit(1) */ + if (!strcmp(val, "unlimited") || !strcmp(val, "infinity")) { cfg = RLIM_INFINITY; } else { const char *err = NULL; - cfg = strtonum(tok, 0, (long long)2 << 31, &err); - if (err) - goto fail; + cfg = strtonum(val, 0, (long long)2 << 31, &err); + if (err) { + logit(LOG_WARNING, "rlimit: invalid %s value: %s", + rlim2str(resource), val); + return; + } } - if (getrlimit(resource, &rlim)) - goto fail; - *set = cfg; - if (setrlimit(resource, &rlim)) - goto fail; - - return; - -fail: - logit(LOG_WARNING, "rlimit: Failed setting rlimit %s", name->name ? : "unknown"); return; error: logit(LOG_WARNING, "rlimit: parse error"); @@ -289,7 +305,7 @@ static void parse_static(char *line) } } -static void parse_dynamic(char *line, struct timeval *mtime) +static void parse_dynamic(char *line, struct rlimit rlimit[], struct timeval *mtime) { char *x; char cmd[CMD_SIZE]; @@ -315,26 +331,26 @@ static void parse_dynamic(char *line, struct timeval *mtime) /* Monitored daemon, will be respawned on exit */ if (MATCH_CMD(line, "service ", x)) { - service_register(SVC_TYPE_SERVICE, x, mtime); + service_register(SVC_TYPE_SERVICE, x, rlimit, mtime); return; } /* One-shot task, will not be respawned */ if (MATCH_CMD(line, "task ", x)) { - service_register(SVC_TYPE_TASK, x, mtime); + service_register(SVC_TYPE_TASK, x, rlimit, mtime); return; } /* Like task but waits for completion, useful w/ [S] */ if (MATCH_CMD(line, "run ", x)) { - service_register(SVC_TYPE_RUN, x, mtime); + service_register(SVC_TYPE_RUN, x, rlimit, mtime); return; } /* Classic inetd service */ if (MATCH_CMD(line, "inetd ", x)) { #ifdef INETD_ENABLED - service_register(SVC_TYPE_INETD, x, mtime); + service_register(SVC_TYPE_INETD, x, rlimit, mtime); #else _e("Finit built with inetd support disabled, cannot register service inetd %s!", x); #endif @@ -343,13 +359,13 @@ static void parse_dynamic(char *line, struct timeval *mtime) /* Read resource limits */ if (MATCH_CMD(line, "rlimit ", x)) { - conf_parse_rlimit(x); + conf_parse_rlimit(x, rlimit); return; } /* Regular or serial TTYs to run getty */ if (MATCH_CMD(line, "tty ", x)) { - tty_register(strip_line(x), mtime); + tty_register(strip_line(x), rlimit, mtime); return; } } @@ -368,6 +384,7 @@ static void tabstospaces(char *line) static int parse_conf_dynamic(char *file, struct timeval *mtime) { FILE *fp; + struct rlimit rlimit[RLIMIT_NLIMITS]; fp = fopen(file, "r"); if (!fp) { @@ -375,6 +392,9 @@ static int parse_conf_dynamic(char *file, struct timeval *mtime) return 1; } + /* Prepare default limits for each service */ + memcpy(rlimit, global_rlimit, sizeof(rlimit)); + _d("Parsing %s", file); while (!feof(fp)) { char line[LINE_SIZE] = ""; @@ -386,7 +406,7 @@ static int parse_conf_dynamic(char *file, struct timeval *mtime) tabstospaces(line); _d("%s", line); - parse_dynamic(line, mtime); + parse_dynamic(line, rlimit, mtime); } fclose(fp); @@ -394,11 +414,19 @@ static int parse_conf_dynamic(char *file, struct timeval *mtime) return 0; } +/* Reads /etc/finit.conf, once at boot */ static int parse_conf(char *file) { FILE *fp; char line[LINE_SIZE] = ""; - char *x; + + /* + * Get current global limits, which may be overridden from both + * finit.conf, for Finit and its services like inetd+getty, and + * *.conf in finit.d/, for each service(s) listed there. + */ + for (int i = 0; i < RLIMIT_NLIMITS; i++) + getrlimit(i, &global_rlimit[i]); fp = fopen(file, "r"); if (!fp) @@ -414,11 +442,17 @@ static int parse_conf(char *file) _d("%s", line); parse_static(line); - parse_dynamic(line, NULL); + parse_dynamic(line, global_rlimit, NULL); } fclose(fp); + /* Set global limits */ + for (int i = 0; i < RLIMIT_NLIMITS; i++) { + if (setrlimit(i, &global_rlimit[i]) == -1) + logit(LOG_WARNING, "rlimit: Failed setting %s", rlim2str(i)); + } + return 0; } @@ -439,6 +473,10 @@ int conf_reload_dynamic(void) return 1; } + /* Update global limits in case operator used prlimit(1) on us */ + for (i = 0; i < RLIMIT_NLIMITS; i++) + getrlimit(i, &global_rlimit[i]); + for (i = 0; i < num; i++) { char *name = e[i]->d_name; char path[LINE_SIZE]; diff --git a/src/conf.h b/src/conf.h index 26c0bf56..fd830d89 100644 --- a/src/conf.h +++ b/src/conf.h @@ -26,6 +26,11 @@ #include "svc.h" +extern struct rlimit global_rlimit[]; + +int str2rlim(char *str); +char *rlim2str(int rlim); + void conf_parse_cmdline (void); int conf_parse_runlevels (char *runlevels); void conf_parse_cond (svc_t *svc, char *cond); diff --git a/src/exec.c b/src/exec.c index 359eacb5..87d17a8e 100644 --- a/src/exec.c +++ b/src/exec.c @@ -234,7 +234,7 @@ int exec_runtask(char *cmd, char *args[]) return execvp(_PATH_BSHELL, argv); } -static void prepare_tty(char *tty, speed_t speed, char *procname) +static void prepare_tty(char *tty, speed_t speed, char *procname, struct rlimit rlimit[]) { struct sigaction sa; struct termios term; @@ -270,6 +270,12 @@ static void prepare_tty(char *tty, speed_t speed, char *procname) sigaction(SIGINT, &sa, NULL); sigaction(SIGQUIT, &sa, NULL); + /* Set configured limits */ + for (int i = 0; i < RLIMIT_NLIMITS; i++) { + if (setrlimit(i, &rlimit[i]) == -1) + logit(LOG_WARNING, "%s: rlimit: Failed setting %s", tty, rlim2str(i)); + } + /* Create new session and process group */ setsid(); @@ -348,7 +354,7 @@ static int activate_console(int noclear, int nowait) * since /bin/login usually only disables ECHO until a password line has * been entered. Upon starting the user's $SHELL the ISIG flag is reset */ -pid_t run_getty(char *tty, char *baud, char *term, int noclear, int nowait) +pid_t run_getty(char *tty, char *baud, char *term, int noclear, int nowait, struct rlimit rlimit[]) { pid_t pid; @@ -362,7 +368,7 @@ pid_t run_getty(char *tty, char *baud, char *term, int noclear, int nowait) logit(LOG_CRIT, "TTY %s: Invalid speed %s", tty, baud); } - prepare_tty(tty, speed, "finit-getty"); + prepare_tty(tty, speed, "finit-getty", rlimit); if (activate_console(noclear, nowait)) _exit(getty(tty, speed, term, NULL)); } @@ -370,7 +376,7 @@ pid_t run_getty(char *tty, char *baud, char *term, int noclear, int nowait) return pid; } -pid_t run_getty2(char *tty, char *cmd, char *args[], int noclear, int nowait) +pid_t run_getty2(char *tty, char *cmd, char *args[], int noclear, int nowait, struct rlimit rlimit[]) { pid_t pid; @@ -400,7 +406,7 @@ pid_t run_getty2(char *tty, char *cmd, char *args[], int noclear, int nowait) dup2(fd, STDERR_FILENO); /* Dunno speed, tell stty() to not mess with it */ - prepare_tty(tty, B0, "getty"); + prepare_tty(tty, B0, "getty", rlimit); if (ioctl(STDIN_FILENO, TIOCSCTTY, 1) < 0) _pe("Failed TIOCSCTTY"); diff --git a/src/finit.c b/src/finit.c index dc143f4d..58cdcfb3 100644 --- a/src/finit.c +++ b/src/finit.c @@ -357,7 +357,7 @@ int main(int argc, char* argv[]) /* Register udevd as a monitored service, started much later */ snprintf(cmd, sizeof(cmd), "[12345] %s -- Device event manager daemon", path); - if (service_register(SVC_TYPE_SERVICE, cmd, NULL)) { + if (service_register(SVC_TYPE_SERVICE, cmd, global_rlimit, NULL)) { _pe("Failed registering %s", path); udev = 0; } diff --git a/src/helpers.h b/src/helpers.h index b677faba..d4378482 100644 --- a/src/helpers.h +++ b/src/helpers.h @@ -26,6 +26,7 @@ #define FINIT_HELPERS_H_ #include +#include #include #include /* Not included by default in musl libc */ #include @@ -71,8 +72,8 @@ int complete (char *cmd, int pid); int run (char *cmd); int run_interactive (char *cmd, char *fmt, ...); int exec_runtask (char *cmd, char *args[]); -pid_t run_getty (char *tty, char *baud, char *term, int noclear, int nowait); -pid_t run_getty2 (char *tty, char *cmd, char *args[], int noclear, int nowait); +pid_t run_getty (char *tty, char *baud, char *term, int noclear, int nowait, struct rlimit rlimit[]); +pid_t run_getty2 (char *tty, char *cmd, char *args[], int noclear, int nowait, struct rlimit rlimit[]); int run_parts (char *dir, char *cmd); static inline void create(char *path, mode_t mode, uid_t uid, gid_t gid) diff --git a/src/service.c b/src/service.c index 64b1edea..61f2c180 100644 --- a/src/service.c +++ b/src/service.c @@ -26,6 +26,7 @@ #include /* isblank() */ #include +#include #include #include #include @@ -192,6 +193,14 @@ static int service_start(svc_t *svc) #endif char *args[MAX_NUM_SVC_ARGS]; + /* Set configured limits */ + for (int i = 0; i < RLIMIT_NLIMITS; i++) { + if (setrlimit(i, &svc->rlimit[i]) == -1) + logit(LOG_WARNING, + "%s: rlimit: Failed setting %s", + svc->cmd, rlim2str(i)); + } + /* Set desired user+group */ if (gid >= 0) setgid(gid); @@ -471,9 +480,10 @@ void service_runlevel(int newlevel) /** * service_register - Register service, task or run commands - * @type: %SVC_TYPE_SERVICE(0), %SVC_TYPE_TASK(1), %SVC_TYPE_RUN(2) - * @svcline: A complete command line with -- separated description text - * @mtime: The modification time if service is loaded from /etc/finit.d + * @type: %SVC_TYPE_SERVICE(0), %SVC_TYPE_TASK(1), %SVC_TYPE_RUN(2) + * @cfg: Configuration, complete command, with -- for description text + * @rlimit: Limits for this service/task/run/inetd, may be global limits + * @mtime: The modification time if service is loaded from /etc/finit.d * * This function is used to register commands to be run on different * system runlevels with optional username. The @type argument details @@ -520,7 +530,7 @@ void service_runlevel(int newlevel) * Returns: * POSIX OK(0) on success, or non-zero errno exit status on failure. */ -int service_register(int type, char *svcline, struct timeval *mtime) +int service_register(int type, char *cfg, struct rlimit rlimit[], struct timeval *mtime) { int i = 0; int id = 1; /* Default to ID:1 */ @@ -534,12 +544,12 @@ int service_register(int type, char *svcline, struct timeval *mtime) svc_t *svc; plugin_t *plugin = NULL; - if (!svcline) { + if (!cfg) { _e("Invalid input argument"); return errno = EINVAL; } - line = strdup(svcline); + line = strdup(cfg); if (!line) return 1; @@ -555,7 +565,7 @@ int service_register(int type, char *svcline, struct timeval *mtime) cmd = strtok(line, " "); if (!cmd) { incomplete: - _e("Incomplete service '%s', cannot register", svcline); + _e("Incomplete service '%s', cannot register", cfg); free(line); return errno = ENOENT; } @@ -718,6 +728,8 @@ recreate: } } #endif + /* Set configured limits */ + memcpy(svc->rlimit, rlimit, sizeof(svc->rlimit)); /* New, recently modified or unchanged ... used on reload. */ svc_check_dirty(svc, mtime); diff --git a/src/service.h b/src/service.h index 51ba4fe5..2f5c63a3 100644 --- a/src/service.h +++ b/src/service.h @@ -28,7 +28,7 @@ #include "svc.h" void service_runlevel (int newlevel); -int service_register (int type, char *line, struct timeval *mtime); +int service_register (int type, char *line, struct rlimit rlimit[], struct timeval *mtime); void service_unregister (svc_t *svc); int service_enabled (svc_t *svc); diff --git a/src/svc.h b/src/svc.h index c055de37..fde506b7 100644 --- a/src/svc.h +++ b/src/svc.h @@ -26,6 +26,7 @@ #define FINIT_SVC_H_ #include /* IPC_CREAT */ +#include #include /* shmat() */ #include /* pid_t */ #include @@ -84,6 +85,9 @@ typedef struct svc { /* Instance specifics */ int job, id; /* JOB:ID */ + /* Limits and scoping */ + struct rlimit rlimit[RLIMIT_NLIMITS]; + /* Service details */ pid_t pid; const svc_state_t state; /* Paused, Reloading, Restart, Running, ... */ diff --git a/src/tty.c b/src/tty.c index 2e813143..ff2545ea 100644 --- a/src/tty.c +++ b/src/tty.c @@ -127,8 +127,9 @@ void tty_sweep(void) /** * tty_register - Register a getty on a device - * @line: Configuration, text after initial "tty" - * @mtime: Modification time, to propagate to lower layers + * @line: Configuration, text after initial "tty" + * @rlimit: Limits for this service/task/run/inetd, may be global limits + * @mtime: Modification time, to propagate to lower layers * * A Finit tty line can use the internal getty implementation or an * external one, like the BusyBox getty for instance. This function @@ -146,7 +147,7 @@ void tty_sweep(void) * Different getty implementations prefer the TTY device argument in * different order, so take care to investigate this first. */ -int tty_register(char *line, struct timeval *mtime) +int tty_register(char *line, struct rlimit rlimit[], struct timeval *mtime) { tty_node_t *entry; int insert = 0, noclear = 0, nowait = 0; @@ -278,6 +279,9 @@ int tty_register(char *line, struct timeval *mtime) if (insert) LIST_INSERT_HEAD(&tty_list, entry, link); + /* Register configured limits */ + memcpy(entry->data.rlimit, rlimit, sizeof(entry->data.rlimit)); + tty_check(entry, mtime); _d("TTY %s is %sdirty", dev, entry->dirty ? "" : "NOT "); @@ -399,9 +403,9 @@ void tty_start(finit_tty_t *tty) } if (!tty->cmd) - tty->pid = run_getty(dev, tty->baud, tty->term, tty->noclear, tty->nowait); + tty->pid = run_getty(dev, tty->baud, tty->term, tty->noclear, tty->nowait, tty->rlimit); else - tty->pid = run_getty2(dev, tty->cmd, tty->args, tty->noclear, tty->nowait); + tty->pid = run_getty2(dev, tty->cmd, tty->args, tty->noclear, tty->nowait, tty->rlimit); } void tty_stop(finit_tty_t *tty) diff --git a/src/tty.h b/src/tty.h index c6610457..50c6cd96 100644 --- a/src/tty.h +++ b/src/tty.h @@ -26,6 +26,7 @@ #define FINIT_TTY_H_ #include +#include #include /* BSD sys/queue.h API */ #define TTY_MAX_ARGS 16 @@ -43,6 +44,9 @@ typedef struct { char *args[TTY_MAX_ARGS]; int pid; + + /* Limits and scoping */ + struct rlimit rlimit[RLIMIT_NLIMITS]; } finit_tty_t; typedef struct tty_node { @@ -61,7 +65,7 @@ void tty_mark (void); void tty_check (tty_node_t *tty, struct timeval *mtime); void tty_sweep (void); -int tty_register (char *line, struct timeval *mtime); +int tty_register (char *line, struct rlimit rlimit[], struct timeval *mtime); int tty_unregister (tty_node_t *tty); tty_node_t *tty_find (char *dev);