[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