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);

Reply via email to