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
6266struct 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) {
438526struct 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
9601122child_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