[git commit] *: don't use getservbyname, it links in a static buffer

Denys Vlasenko vda.linux at googlemail.com
Wed Feb 25 01:35:32 UTC 2026


commit: https://git.busybox.net/busybox/commit/?id=ad88be9e7ea4ae74b5b012d75e5b92ff12ca273d
branch: https://git.busybox.net/busybox/log/?h=master

function                                             old     new   delta
bb_get_servport_by_name                                -     348    +348
bb_lookup_port                                        83     111     +28
reread_config_file                                   886     907     +21
static.se                                             16       -     -16
getservbyname                                         53       -     -53
getservbyname_r                                      284       -    -284
------------------------------------------------------------------------------
(add/remove: 2/5 grow/shrink: 2/0 up/down: 397/-353)           Total: 44 bytes
   text	   data	    bss	    dec	    hex	filename
1080084	    555	   5024	1085663	 1090df	busybox_old
1080144	    555	   4992	1085691	 1090fb	busybox_unstripped

Signed-off-by: Denys Vlasenko <vda.linux at googlemail.com>
---
 include/libbb.h                 |   4 +-
 libbb/bb_get_servname_by_port.c |   6 +-
 libbb/bb_get_servport_by_name.c | 129 ++++++++++++++++++++++++++++++++++++++++
 libbb/xconnect.c                |  21 -------
 networking/inetd.c              |  22 +++----
 5 files changed, 145 insertions(+), 37 deletions(-)

diff --git a/include/libbb.h b/include/libbb.h
index e282066b8..0fee62929 100644
--- a/include/libbb.h
+++ b/include/libbb.h
@@ -911,9 +911,11 @@ char* xmalloc_sockaddr2hostonly_noport(const struct sockaddr *sa) FAST_FUNC RETU
 /* inet_[ap]ton on steroids */
 char* xmalloc_sockaddr2dotted(const struct sockaddr *sa) FAST_FUNC RETURNS_MALLOC;
 char* xmalloc_sockaddr2dotted_noport(const struct sockaddr *sa) FAST_FUNC RETURNS_MALLOC;
-// NB: unlike getservbyport, port parameter is NOT in network order
+// NB: unlike getservbyport, port parameter/retval are in host, NOT network order!
 #define getservbyport dont_use_getservbyport_uses_global_buffer
 char* bb_get_servname_by_port(char **p_etc_services, int port, const char *type) FAST_FUNC RETURNS_MALLOC;
+#define getservbyname dont_use_getservbyname_uses_global_buffer
+int bb_get_servport_by_name(char **p_etc_services, const char *name, const char *type) FAST_FUNC;
 // "old" (ipv4 only) API
 // users: traceroute.c hostname.c - use _list_ of all IPs
 struct hostent *xgethostbyname(const char *name) FAST_FUNC;
diff --git a/libbb/bb_get_servname_by_port.c b/libbb/bb_get_servname_by_port.c
index 3846072f9..970a718fe 100644
--- a/libbb/bb_get_servname_by_port.c
+++ b/libbb/bb_get_servname_by_port.c
@@ -9,11 +9,9 @@
 //kbuild:lib-$(CONFIG_PSCAN) += bb_get_servname_by_port.o
 #include "libbb.h"
 
-//Rationale for exising:
+//Rationale for existing:
 //#define getservbyport dont_use_getservbyport_uses_global_buffer
 //(for example: -280 bytes on musl, x86-32)
-//TODO:
-//avoid getservbyname() as well
 
 char* FAST_FUNC bb_get_servname_by_port(char **p_etc_services, int port, const char *type)
 {
@@ -45,7 +43,7 @@ char* FAST_FUNC bb_get_servname_by_port(char **p_etc_services, int port, const c
 			end = is_prefixed_with(end, type);
 			if (!end
 			 || !(isspace(*end) || *end == '\0'
-			    || *end == '#') // glibc treats "http 80/tcp#COMMENT" as valid
+			    || *end == '#') // glibc treats "http 80/tcp#COMMENT" (no space!) as valid
 			) {
 				goto next;
 			}
diff --git a/libbb/bb_get_servport_by_name.c b/libbb/bb_get_servport_by_name.c
new file mode 100644
index 000000000..96580c900
--- /dev/null
+++ b/libbb/bb_get_servport_by_name.c
@@ -0,0 +1,129 @@
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 2026 Denys Vlasenko
+ *
+ * Licensed under GPLv2, see file LICENSE in this source tree.
+ */
+//kbuild:lib-y += bb_get_servport_by_name.o
+//kbuild:lib-y += bb_get_servport_by_name.o
+#include "libbb.h"
+
+//Rationale for existing:
+//#define getservbyname dont_use_getservbyname_uses_global_buffer
+//(for example: -32 BSS bytes on musl, x86-32)
+
+int FAST_FUNC bb_get_servport_by_name(char **p_etc_services, const char *name, const char *proto)
+{
+	unsigned namelen;
+	char *sp;
+
+	if (!name[0]) // This can hang the search (strstr advances by 0 bytes)
+		return -1; // bad service name form: ""
+	// Any other bogosity to reject?
+	// Or just enforce isalnum_or_dash_or_slash(name[i])?
+	// Yes, service name like "cl/1" is allowed and _exists_.
+	//
+	// Protect against finding service "www#c" in "http 10/tcp www#c" line
+	//if (strchr(name, '#'))
+	//	return -1; // bad service name form
+	//^^^^ the main code already catches this possibility.
+	//
+	// Current code allows e.g. service names like "http 80/tcp"
+	// to map to port 80. Probably harmless?
+	//if (skip_non_whitespace(name)[0])
+	//	return -1; // bad service name form (has whitespace)
+
+	sp = *p_etc_services;
+	if (!sp) {
+//TODO: we need mmap_entire_file() for this use case!
+		sp = xmalloc_open_read_close("/etc/services", NULL);
+		if (!sp)
+			return -1;
+		*p_etc_services = sp;
+	}
+	namelen = strlen(name);
+	while (*sp) {
+		const char *pnstr;
+		char *start, *end;
+		unsigned n;
+
+		// First, find a possible service name without regard for line separators (!)
+		start = strstr(sp, name);
+		if (!start)
+			return -1;
+		sp = start + namelen;
+		if (start != *p_etc_services && !isspace(start[-1])) {
+			// There is a char before it, and it's not whitespace
+			continue;
+		}
+		if (!(isspace(*sp) || *sp == '\0' || *sp == '#'))
+			// After it: not whitespace/EOF/comment
+			continue;
+		// The found substring _is_ correctly delimited before and after
+
+		// Find beginning of the line we are on
+		while (start != *p_etc_services && start[-1] != '\n')
+			start--;
+		start = skip_whitespace(start);
+		// Is there a comment char between start of line and "service name"?
+		if (memchr(start, '#', sp - start)) {
+			// The "service name" we found is actually in comment / has comment in it.
+			// Also rejects names with #:
+			// service "www#c" won't be found even on "http 80/tcp www#c" line
+			continue;
+		}
+		// Is the [start...sp) of the form "SERVNAME NUM/PROTO[ ALIAS[ ALIAS...][ ]]"?
+		pnstr = skip_whitespace(skip_non_whitespace(start)); // go to NUM...
+
+		// I've seen this line in /etc/services of a real-world machine:
+		//914c/g 211/tcp 914c-g
+		// Now consider just a bit more pathological example:
+		//914c/tcp 914/tcp 914/tcp
+		// If we search for service name "914/tcp",
+		// we'll find it at second word first, yet we must not reject this line
+		// because the THIRD word also matches, and it's a valid match (tested on glibc!).
+		// But in this case we must not match:
+		//914c/tcp 914/tcp something-else
+		if (sp - namelen == pnstr)
+			continue; // the match is at NUM..., try matching further
+
+		n = bb_strtou(pnstr, &end, 10);
+		if (n > 0xffff || *end != '/')
+			continue; // NUM... part is not a valid port#, or has no slash
+
+		if (proto) {
+			end++; // points to PROTO
+			end = is_prefixed_with(end, proto);
+			if (!end
+			 || !(isspace(*end) || *end == '\0'
+			    || *end == '#') // glibc treats "http 80/tcp#COMMENT" (no space!) as valid
+			) {
+				continue; // PROTO does not match
+			}
+		}
+		// By now, either WORD or one of the ALIASes has to be the part
+		// which was found by strstr()!
+		return n;
+	}
+	return -1;
+}
+
+// Return port number for a service.
+// If "port" is a number use it as the port.
+// If "port" is a name it is looked up in /etc/services.
+// if NULL, return default_port
+unsigned FAST_FUNC bb_lookup_port(const char *port, const char *protocol, unsigned port_nr)
+{
+	if (port) {
+		port_nr = bb_strtou(port, NULL, 10);
+		if (errno || port_nr > 65535) {
+			char *p_etc_services = NULL;
+			port_nr = bb_get_servport_by_name(&p_etc_services, port, protocol);
+			if (port_nr > 0xffff) // -1?
+				bb_error_msg_and_die("bad port '%s'", port);
+			free(p_etc_services);
+		}
+	}
+	return (uint16_t)port_nr;
+}
diff --git a/libbb/xconnect.c b/libbb/xconnect.c
index 0e0b247b8..095f7ff47 100644
--- a/libbb/xconnect.c
+++ b/libbb/xconnect.c
@@ -113,29 +113,8 @@ void FAST_FUNC xconnect(int s, const struct sockaddr *s_addr, socklen_t addrlen)
 	}
 }
 
-/* Return port number for a service.
- * If "port" is a number use it as the port.
- * If "port" is a name it is looked up in /etc/services.
- * if NULL, return default_port
- */
-unsigned FAST_FUNC bb_lookup_port(const char *port, const char *protocol, unsigned port_nr)
-{
-	if (port) {
-		port_nr = bb_strtou(port, NULL, 10);
-		if (errno || port_nr > 65535) {
-			struct servent *tserv = getservbyname(port, protocol);
-			if (!tserv)
-				bb_error_msg_and_die("bad port '%s'", port);
-			port_nr = ntohs(tserv->s_port);
-		}
-	}
-	return (uint16_t)port_nr;
-}
-
-
 /* "New" networking API */
 
-
 int FAST_FUNC get_nport(const struct sockaddr *sa)
 {
 #if ENABLE_FEATURE_IPV6
diff --git a/networking/inetd.c b/networking/inetd.c
index e63edcd9d..6220a08e3 100644
--- a/networking/inetd.c
+++ b/networking/inetd.c
@@ -969,9 +969,8 @@ static void reread_config_file(int sig UNUSED_PARAM)
 	servtab_t *sep, *cp, **sepp;
 	len_and_sockaddr *lsa;
 	sigset_t omask;
-	unsigned n;
-	uint16_t port;
 	int save_errno = errno;
+	char *p_etc_services = NULL;
 
 	if (!reopen_config_file())
 		goto ret;
@@ -1039,7 +1038,9 @@ static void reread_config_file(int sig UNUSED_PARAM)
 			break;
 
 		default: /* case AF_INET, case AF_INET6 */
-			n = bb_strtou(sep->se_service, NULL, 10);
+		{
+			unsigned portno;
+			portno = bb_strtou(sep->se_service, NULL, 10);
 #if ENABLE_FEATURE_INETD_RPC
 			if (is_rpc_service(sep)) {
 				sep->se_rpcprog = n;
@@ -1059,26 +1060,23 @@ static void reread_config_file(int sig UNUSED_PARAM)
 			}
 #endif
 			/* what port to listen on? */
-			port = htons(n);
-			if (errno || n > 0xffff) { /* se_service is not numeric */
+			if (errno || portno > 0xffff) { /* se_service is not numeric */
 				char protoname[4];
-				struct servent *sp;
 				/* can result only in "tcp" or "udp": */
 				safe_strncpy(protoname, sep->se_proto, 4);
-				sp = getservbyname(sep->se_service, protoname);
-				if (sp == NULL) {
+				portno = bb_get_servport_by_name(&p_etc_services, sep->se_service, protoname);
+				if (portno > 0xffff) {
 					bb_error_msg("%s/%s: unknown service",
 							sep->se_service, sep->se_proto);
 					goto next_cp;
 				}
-				port = sp->s_port;
 			}
 			if (LONE_CHAR(sep->se_local_hostname, '*')) {
 				lsa = xzalloc_lsa(sep->se_family);
-				set_nport(&lsa->u.sa, port);
+				set_nport(&lsa->u.sa, htons(portno));
 			} else {
 				lsa = host_and_af2sockaddr(sep->se_local_hostname,
-						ntohs(port), sep->se_family);
+						portno, sep->se_family);
 				if (!lsa) {
 					bb_error_msg("%s/%s: unknown host '%s'",
 						sep->se_service, sep->se_proto,
@@ -1087,6 +1085,7 @@ static void reread_config_file(int sig UNUSED_PARAM)
 				}
 			}
 			break;
+		}//default:
 		} /* end of "switch (sep->se_family)" */
 
 		/* did lsa change? Then close/open */
@@ -1134,6 +1133,7 @@ static void reread_config_file(int sig UNUSED_PARAM)
 	}
 	restore_sigmask(&omask);
  ret:
+	free(p_etc_services);
 	errno = save_errno;
 }
 


More information about the busybox-cvs mailing list