Control: tags -1 + upstream patch Hi Alexander,
On Thu, Mar 26, 2026 at 12:13:37AM +0100, Alexander Motzkau wrote: > Starting with Debian Trixie, a systemd service with LimitNOFILE=infinity > will get an fd limit of 2^30 file descriptors (see #1029152). If such a > service (like inn2) is then executing a script via super, super will > allocate 4GB of memory just for option parsing (4 bytes per possible > file descriptor for storing a boolean, see option_local_clear_settings()). It seems to me that this is a reasonable complaint but that a fair workaround on trixie would be to arrange for super to be invoked via '/usr/bin/prlimit -n1024 or similar' for these scripts. Hi, Robert, I have created a patch for this that stores the fd options in a linked list instead of an array (patch 20 attached). Additionally I attach patch 21 as an optimisation to use close_range(2) rather than invoking a syscall for every possible fd. In writing this solution I came across a big in the TimeList handling and patch 19 fixes this. For a more convenient view of the patches, they are assembled on top of the dgit patches applied view in my fork, here: https://salsa.debian.org/abower/super/-/commits/close_range I hope this is of use! Andrew
From: Andrew Bower <[email protected]> Date: Sat, 11 Apr 2026 12:40:28 +0100 Subject: Fix free_TimeList() to walk its list --- time.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/time.c b/time.c index c01e6a9..5a583a3 100644 --- a/time.c +++ b/time.c @@ -206,7 +206,7 @@ TimeList *tl; return; tlp = tl->next; tl->next = NULL; - for (tl=tl->next ; tl; tl = tlp) { + while ((tl = tlp)) { tlp = tl->next; free(tl); }
From: Andrew Bower <[email protected]> Date: Sat, 11 Apr 2026 12:41:11 +0100 Subject: Store fds to keep open in a linked list Bug-Debian: https://bugs.debian.org/1131890 --- options.c | 20 +++++--------------- super.c | 11 +++++------ super.h | 10 +++++++++- utils.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 65 insertions(+), 22 deletions(-) diff --git a/options.c b/options.c index fa24144..b8b539c 100644 --- a/options.c +++ b/options.c @@ -259,11 +259,9 @@ option_local_clear_settings() { /* Clear local settings */ int i; - int maxfd = MAXFD; if (debug>1) fprintf(stderr, "\toption_local_clear_settings()\n"); - maxfd = MAXFD; if (localinfo.info) free(localinfo.info); localinfo.info = NULL; @@ -302,15 +300,7 @@ option_local_clear_settings() if (localinfo.fdlist) free(localinfo.fdlist); localinfo.fdlist = NULL; - if (!localinfo.fd) { - localinfo.fd = (int *) malloc(sizeof(int) * (maxfd + 1)); - if (!localinfo.fd) - return Error(1, 0, - "$$\n\tFailed to malloc space for file descriptor list: "); - } - for (i=0; i<=maxfd; i++) { - localinfo.fd[i] = 0; - } + free_IntList(&localinfo.fd); localinfo.mailcmd[0] = '\0'; localinfo.mail_success = -1; /* Don't let local groups default to global groups, because then we @@ -691,7 +681,6 @@ int isglobal; int n; int iwd; char *p, *wd; - int maxfd = MAXFD; if (debug) fprintf(stderr, "\toption:fd=%s (%s)\n", @@ -703,9 +692,10 @@ int isglobal; "Failed to malloc space for copy of fd=<%s>\n", s); for (iwd=0; (wd=optionlist[iwd]); iwd++) { - if (((n=strtol(wd, &p, 0)) >= 0) && n <= maxfd && p != wd) - localinfo.fd[n] = 1; - else + if (((n=strtol(wd, &p, 0)) >= 0) && p != wd) { + if (InsertIntList(n, &localinfo.fd)) + return Error(0, 0, "Failed to record fd option\n"); + } else return Error(0, 0, "$$\n\tRidiculous value in file descriptor list: `%s'\n", word); diff --git a/super.c b/super.c index 5a14fe4..71e4028 100644 --- a/super.c +++ b/super.c @@ -1392,24 +1392,23 @@ char *cmd; /* name of command being started */ fd_log = globalinfo.log.fp ? fileno(globalinfo.log.fp) : -1; maxfd = MAXFD; + for (fd=3; fd <= maxfd; fd++) { + IntList *node; + for (node = localinfo.fd.next; node && node->i != fd; node = node->next); + if (!node && fd != fd_log) #ifdef HAVE_FCNTL_H #ifndef FD_CLOEXEC #define FD_CLOEXEC 1 #endif - for (fd=3; fd <= maxfd; fd++) - if (localinfo.fd[fd] == 0 && fd != fd_log) (void) fcntl(fd, F_SETFD, FD_CLOEXEC); #else #ifdef HAVE_IOCTL_FIOCLEX - for (fd=3; fd <= maxfd; fd++) - if (localinfo.fd[fd] == 0 && fd != fd_log) (void) ioctl(fd, FIOCLEX, NULL); #else - for (fd=3; fd <= maxfd; fd++) - if (localinfo.fd[fd] == 0 && fd != fd_log) (void) close(fd); #endif #endif + } for (i=0; i<NSIG; i++) (void) signal(i, SIG_DFL); diff --git a/super.h b/super.h index 385336c..ff03a42 100644 --- a/super.h +++ b/super.h @@ -235,6 +235,12 @@ struct timeList { }; typedef struct timeList TimeList; +struct intList { + int i; + struct intList *next; +}; +typedef struct intList IntList; + /* Linked open files */ struct fileList { char *givenname; /* filename, as given in the parent file */ @@ -460,7 +466,7 @@ struct localInfo { char mailcmd[500]; /* Information for logging via mail */ int mail_success; /* bool: mail on successful tries? (-1 = unknown) */ - int *fd; /* descriptors from fdlist string */ + IntList fd; /* descriptors from fdlist string */ AuthInfo authinfo; /* authentication requirements on this command */ }; typedef struct localInfo LocalInfo; @@ -554,6 +560,7 @@ int findgroup P__(( char *grouplabel )); int fixup_fullpath P__((int , char *, char *, char *, int )); void free_SimpleList P__(( SimpleList *)); void free_Simple2List P__(( Simple2List *)); +void free_IntList P__(( IntList *)); void free_TimeList P__(( TimeList *)); int get_canonical_hostname P__((char *buf, int len)); int get_encrypted_pw P__(( void )); @@ -580,6 +587,7 @@ void init_globalinfo P__( (void) ); void init_localinfo P__( (void) ); int init_userinfo P__( (void) ); int InsertCondition P__(( char *, char *, int )); +int InsertIntList P__(( int, IntList * )); int InsertTimeList P__(( char *, char **, TimeList *, char *, int )); int InsertUserList P__((char *, char **, Simple2List *, SimpleList *, int)); void logmsg P__(( char * cmd, char ** args )); diff --git a/utils.c b/utils.c index 59c84a8..a83b5fe 100644 --- a/utils.c +++ b/utils.c @@ -999,6 +999,52 @@ ARnext( return NULL; } +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/* Inserts an elements into a IntList. + */ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +int +InsertIntList(i, il) +int i; /* The integer to insert into the list */ +IntList *il; /* Insert int list elements at il->next */ +{ + /* Inserts integer into list. + * -1 on malloc error, or similar; + * 0 otherwise. + * Side effect: inserts the value into the list. + */ + IntList *new; + + new = (IntList *) malloc(sizeof(IntList)); + if (!new) + return Error(0, 0, "$$\n\tFailed to malloc space for integer entry\n"); + new->next = il->next; + new->i = i; + il->next = new; + return 0; +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/* Frees all elements in a IntList, except the one it's given. + * The "next" field of that element is set NULL. + */ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +void +free_IntList(il) +IntList *il; +{ + IntList *ilp; + + if (!il || !il->next) + return; + ilp = il->next; + il->next = NULL; + while ((il = ilp)) { + ilp = il->next; + free(il); + } +} + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Copy up to n-1 characters into "to"; then null-terminate. * Return 1 if all characters fitted into "to", else return 0.
From: Andrew Bower <[email protected]> Date: Sat, 11 Apr 2026 13:43:42 +0100 Subject: Optimise closing many fds with close_range(2) --- config.h.in | 3 +++ configure.in | 1 + localsys.h | 5 +++++ super.c | 16 ++++++++++++++++ 4 files changed, 25 insertions(+) diff --git a/config.h.in b/config.h.in index 4981cd1..fb27b96 100644 --- a/config.h.in +++ b/config.h.in @@ -122,6 +122,9 @@ /* Define if your system has <limits.h> */ #undef HAVE_LIMITS_H +/* Define if your system has <linux/close_range.h> */ +#undef HAVE_LINUX_CLOSE_RANGE_H + /* Define if your system has <locale.h> */ #undef HAVE_LOCALE_H diff --git a/configure.in b/configure.in index eb9b688..97e7a93 100644 --- a/configure.in +++ b/configure.in @@ -114,6 +114,7 @@ AC_HAVE_HEADERS(errno.h stdlib.h limits.h string.h standards.h \ memory.h malloc.h pwdadj.h sgtty.h sys/time.h stdarg.h syslog.h \ sys/ioctl.h termio.h termios.h \ arpa/inet.h net/route.h net/if.h netinet/in.h \ + linux/close_range.h \ sys/types.h sys/bsdtypes.h sys/label.h sys/audit.h \ sys/filio.h sys/wait.h sys/param.h sys/security.h \ sys/socket.h sys/sysinfo.h sys/systeminfo.h sys/utsname.h ) diff --git a/localsys.h b/localsys.h index 53768c1..4b14593 100644 --- a/localsys.h +++ b/localsys.h @@ -291,6 +291,11 @@ extern int sysinfo(); #define S_IWOTH 0000002 #endif +#ifdef HAVE_LINUX_CLOSE_RANGE_H +#include <linux/close_range.h> +extern int close_range(unsigned int first, unsigned int last, int flags); +#endif + #ifdef HAVE_MEMORY_H #include <unistd.h> #endif diff --git a/super.c b/super.c index 71e4028..f9d4bec 100644 --- a/super.c +++ b/super.c @@ -1392,6 +1392,21 @@ char *cmd; /* name of command being started */ fd_log = globalinfo.log.fp ? fileno(globalinfo.log.fp) : -1; maxfd = MAXFD; +#ifdef HAVE_LINUX_CLOSE_RANGE_H + for (fd=3; fd >= 3 && fd <= maxfd; fd++) { + IntList *node; + unsigned int max = ~0U; /* This will become the next fd NOT to close */ + for (node = localinfo.fd.next; node; node = node->next) { + if (node->i >= fd && (unsigned int) node->i < max) + max = node->i; + } + if (fd_log >= fd && fd_log < max) + max = fd_log; + if (fd != max) + (void) close_range(fd, max - 1, CLOSE_RANGE_CLOEXEC); + fd = max; + } +#else for (fd=3; fd <= maxfd; fd++) { IntList *node; for (node = localinfo.fd.next; node && node->i != fd; node = node->next); @@ -1409,6 +1424,7 @@ char *cmd; /* name of command being started */ #endif #endif } +#endif for (i=0; i<NSIG; i++) (void) signal(i, SIG_DFL);

