Skip to content

Commit 886c58d

Browse files
committed
main: add rlimit support via UCS configuration
Parse RLIMIT:TYPE:SOFT:HARD entries from the UCS block and apply them via setrlimit(2) before privilege drop in setup_exec_env(). The type string (e.g. RLIMIT_NOFILE) is resolved to a POSIX resource integer using a static lookup table. Limits are stored in two parallel dynamically allocated arrays inside struct process_config: rlimits[] for the struct rlimit values and rlimit_resources[] for the resource integers. Both are freed on cleanup. This is the urunit side of the OCI rlimits feature. The urunc side that serializes these entries is in urunc-dev/urunc#558. Signed-off-by: namansh70747 <namansh70747@gmail.com>
1 parent 8913883 commit 886c58d

1 file changed

Lines changed: 175 additions & 9 deletions

File tree

src/main.c

Lines changed: 175 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242

4343
#include <sys/wait.h>
4444
#include <sys/stat.h>
45+
#include <sys/resource.h>
4546

4647
#include <errno.h>
4748
#include <string.h>
@@ -57,6 +58,9 @@ struct process_config {
5758
uint32_t uid;
5859
uint32_t gid;
5960
char *wdir;
61+
struct rlimit *rlimits; // array of rlimit structs
62+
int *rlimit_resources; // parallel array: the RLIMIT_* int constants
63+
size_t rlimits_count;
6064
};
6165

6266
struct app_exec_config {
@@ -415,20 +419,104 @@ int get_string_val(char *str, char **value) {
415419
return -1;
416420
}
417421

422+
// rlimit_type_from_str: Converts an OCI rlimit type string (e.g. "RLIMIT_NOFILE")
423+
// to the corresponding POSIX integer constant.
424+
//
425+
// Return value: the integer constant, or -1 if unknown.
426+
static int rlimit_type_from_str(const char *s) {
427+
struct { const char *name; int val; } table[] = {
428+
{ "RLIMIT_AS", RLIMIT_AS },
429+
{ "RLIMIT_CORE", RLIMIT_CORE },
430+
{ "RLIMIT_CPU", RLIMIT_CPU },
431+
{ "RLIMIT_DATA", RLIMIT_DATA },
432+
{ "RLIMIT_FSIZE", RLIMIT_FSIZE },
433+
{ "RLIMIT_LOCKS", RLIMIT_LOCKS },
434+
{ "RLIMIT_MEMLOCK", RLIMIT_MEMLOCK },
435+
{ "RLIMIT_MSGQUEUE", RLIMIT_MSGQUEUE },
436+
{ "RLIMIT_NICE", RLIMIT_NICE },
437+
{ "RLIMIT_NOFILE", RLIMIT_NOFILE },
438+
{ "RLIMIT_NPROC", RLIMIT_NPROC },
439+
{ "RLIMIT_RSS", RLIMIT_RSS },
440+
{ "RLIMIT_RTPRIO", RLIMIT_RTPRIO },
441+
{ "RLIMIT_RTTIME", RLIMIT_RTTIME },
442+
{ "RLIMIT_SIGPENDING", RLIMIT_SIGPENDING },
443+
{ "RLIMIT_STACK", RLIMIT_STACK },
444+
};
445+
for (size_t i = 0; i < sizeof(table)/sizeof(table[0]); i++) {
446+
if (strcmp(s, table[i].name) == 0)
447+
return table[i].val;
448+
}
449+
return -1;
450+
}
451+
452+
// get_rlimit_vals: Parses "RLIMIT:<type>:<soft>:<hard>" into resource int,
453+
// soft limit, and hard limit.
454+
//
455+
// Return value: 0 on success, -1 on failure.
456+
static int get_rlimit_vals(char *str, int *resource, rlim_t *soft, rlim_t *hard) {
457+
// str is "RLIMIT:<type>:<soft>:<hard>"
458+
// Skip the leading "RLIMIT:" prefix (7 bytes)
459+
char *p = str + 7;
460+
char *colon1 = strchr(p, ':');
461+
char *colon2 = NULL;
462+
463+
if (!colon1) {
464+
fprintf(stderr, "Malformed RLIMIT line (missing type/soft separator): %s\n", str);
465+
return -1;
466+
}
467+
*colon1 = '\0';
468+
colon2 = strchr(colon1 + 1, ':');
469+
if (!colon2) {
470+
fprintf(stderr, "Malformed RLIMIT line (missing soft/hard separator): %s\n", str);
471+
*colon1 = ':'; // restore
472+
return -1;
473+
}
474+
*colon2 = '\0';
475+
476+
*resource = rlimit_type_from_str(p);
477+
if (*resource < 0) {
478+
fprintf(stderr, "Unknown rlimit type: %s\n", p);
479+
*colon1 = ':'; *colon2 = ':';
480+
return -1;
481+
}
482+
483+
char *end = NULL;
484+
errno = 0;
485+
unsigned long long soft_val = strtoull(colon1 + 1, &end, 10);
486+
if (errno == ERANGE || *end != '\0') {
487+
fprintf(stderr, "Invalid soft rlimit value: %s\n", colon1 + 1);
488+
*colon1 = ':'; *colon2 = ':';
489+
return -1;
490+
}
491+
errno = 0;
492+
unsigned long long hard_val = strtoull(colon2 + 1, &end, 10);
493+
if (errno == ERANGE || *end != '\0') {
494+
fprintf(stderr, "Invalid hard rlimit value: %s\n", colon2 + 1);
495+
*colon1 = ':'; *colon2 = ':';
496+
return -1;
497+
}
498+
499+
*soft = (rlim_t)soft_val;
500+
*hard = (rlim_t)hard_val;
501+
*colon1 = ':'; *colon2 = ':'; // restore (strtok already NUL-terminated line)
502+
return 0;
503+
}
504+
418505
// parse_process_config: Parses a list with the following format:
419506
// UCS
420507
// UID:<uid>
421508
// GID:<gid>
422509
// WD:<working directory>
510+
// RLIMIT:<type>:<soft>:<hard>
423511
// UCE
424512
// It is important to note, that this function will alter the given list,
425513
// replacing the new line characters with the end of string '\0' character.
426514
// The funtion returns a dynamically allocated memory and the caller is
427515
// responsible to free that memory.
428516
//
429517
// Arguments:
430-
// 1. string_area: The list with in the aformentioned format.
431-
// 2. max_sz: The max possible size of the list.
518+
// 1. string_area: The list with in the aformentioned format.
519+
// 2. max_sz: The max possible size of the list.
432520
//
433521
// Return value:
434522
// On success it returns a pointer to a dynamically allocated memory that
@@ -438,6 +526,7 @@ int get_string_val(char *str, char **value) {
438526
struct process_config *parse_process_config(char **string_area, size_t max_sz) {
439527
struct process_config *conf = NULL;
440528
char *tmp_field = NULL;
529+
size_t total_rlimits = 0;
441530

442531
conf = malloc(sizeof(struct process_config));
443532
if (!conf) {
@@ -446,6 +535,46 @@ struct process_config *parse_process_config(char **string_area, size_t max_sz) {
446535
}
447536
memset(conf, 0, sizeof(struct process_config));
448537
conf->wdir = NULL; // Sanity
538+
conf->rlimits = NULL;
539+
conf->rlimit_resources = NULL;
540+
conf->rlimits_count = 0;
541+
542+
// Pre-count how many RLIMIT: lines are in the UCS block so we can
543+
// allocate the arrays exactly once instead of using realloc.
544+
total_rlimits = 0;
545+
{
546+
char *scan = *string_area;
547+
size_t remaining = max_sz;
548+
while (remaining > 7 && *scan != '\0') {
549+
if (memcmp(scan, "RLIMIT:", 7) == 0)
550+
total_rlimits++;
551+
// Advance to next line
552+
while (remaining > 0 && *scan != '\n' && *scan != '\0') {
553+
scan++;
554+
remaining--;
555+
}
556+
if (remaining > 0 && *scan == '\n') {
557+
scan++;
558+
remaining--;
559+
}
560+
}
561+
}
562+
563+
if (total_rlimits > 0) {
564+
conf->rlimits = malloc(total_rlimits * sizeof(struct rlimit));
565+
if (!conf->rlimits) {
566+
fprintf(stderr, "Failed to allocate memory for rlimits array\n");
567+
free(conf);
568+
return NULL;
569+
}
570+
conf->rlimit_resources = malloc(total_rlimits * sizeof(int));
571+
if (!conf->rlimit_resources) {
572+
fprintf(stderr, "Failed to allocate memory for rlimit_resources array\n");
573+
free(conf->rlimits);
574+
free(conf);
575+
return NULL;
576+
}
577+
}
449578

450579
tmp_field = strtok(*string_area, "\n");
451580
// Discard the first string since it is the special string "UCS"
@@ -461,26 +590,43 @@ struct process_config *parse_process_config(char **string_area, size_t max_sz) {
461590
fprintf(stderr, "Failed to retreive UID information from %s\n", tmp_field);
462591
break;
463592
}
464-
} else if (memcmp(tmp_field, "GID", 3) == 0) {
593+
} else if (memcmp(tmp_field, "GID", 3) == 0) {
465594
ret = get_uint_val(tmp_field, &(conf->gid));
466595
if (ret != 0) {
467596
fprintf(stderr, "Failed to retreive GID information from %s\n", tmp_field);
468597
break;
469598
}
470-
} else if (memcmp(tmp_field, "WD", 2) == 0) {
599+
} else if (memcmp(tmp_field, "WD", 2) == 0) {
471600
ret = get_string_val(tmp_field, &(conf->wdir));
472601
if (ret != 0) {
473602
fprintf(stderr, "Failed to retreive WD information from %s\n", tmp_field);
474603
break;
475604
}
476-
} else if (memcmp(tmp_field, "UCE", 3) == 0) {
605+
} else if (memcmp(tmp_field, "RLIMIT:", 7) == 0) {
606+
int resource = 0;
607+
rlim_t soft = 0, hard = 0;
608+
ret = get_rlimit_vals(tmp_field, &resource, &soft, &hard);
609+
if (ret != 0) {
610+
fprintf(stderr, "Failed to parse RLIMIT line: %s\n", tmp_field);
611+
break;
612+
}
613+
size_t n = conf->rlimits_count;
614+
conf->rlimits[n].rlim_cur = soft;
615+
conf->rlimits[n].rlim_max = hard;
616+
conf->rlimit_resources[n] = resource;
617+
conf->rlimits_count++;
618+
} else if (memcmp(tmp_field, "UCE", 3) == 0) {
477619
*string_area = tmp_field + 4; // 4 bytes for the "UCE" string
478620
return conf;
479621
}
480622

481623
tmp_field = strtok(NULL, "\n");
482624
}
483625

626+
// We only reach here if parsing failed (UCE was never found).
627+
// Clean up all allocated memory before returning NULL.
628+
free(conf->rlimits);
629+
free(conf->rlimit_resources);
484630
free(conf);
485631
return NULL;
486632
}
@@ -877,7 +1023,7 @@ int manual_execvpe(const char *env_path, const char *file_bin, char *const argv[
8771023
// the process_conf argument.
8781024
//
8791025
// Arguments:
880-
// 1. process_conf: The config to apply with uid/gid and CWD.
1026+
// 1. process_conf: The config to apply with uid/gid, CWD and rlimits.
8811027
//
8821028
// Return value:
8831029
// On success 0 is returned.
@@ -890,6 +1036,22 @@ int setup_exec_env(struct process_config *process_conf) {
8901036
return 0;
8911037
}
8921038

1039+
// Apply rlimits before dropping privileges, because some rlimit
1040+
// types (e.g. RLIMIT_NOFILE) can only be raised while still root.
1041+
// After setuid() we may no longer have permission to raise hard limits.
1042+
for (size_t i = 0; i < process_conf->rlimits_count; i++) {
1043+
DEBUG_PRINTF("Setting rlimit resource %d soft=%lu hard=%lu\n",
1044+
process_conf->rlimit_resources[i],
1045+
(unsigned long)process_conf->rlimits[i].rlim_cur,
1046+
(unsigned long)process_conf->rlimits[i].rlim_max);
1047+
ret = setrlimit(process_conf->rlimit_resources[i],
1048+
&process_conf->rlimits[i]);
1049+
if (ret < 0) {
1050+
perror("set rlimit");
1051+
return 1;
1052+
}
1053+
}
1054+
8931055
DEBUG_PRINTF("Setting gid to %d\n", process_conf->gid);
8941056
ret = setgid(process_conf->gid);
8951057
if (ret < 0) {
@@ -959,9 +1121,13 @@ int child_func(char *argv[]) {
9591121
// If we returned something went wrong
9601122
child_func_free:
9611123
free(config_buf);
962-
free(app_config->envs);
963-
free(app_config->pr_conf);
964-
free(app_config);
1124+
if (app_config) {
1125+
free(app_config->envs);
1126+
free(app_config->pr_conf->rlimits);
1127+
free(app_config->pr_conf->rlimit_resources);
1128+
free(app_config->pr_conf);
1129+
free(app_config);
1130+
}
9651131

9661132
return ret;
9671133
}

0 commit comments

Comments
 (0)