From 3681d2883de25286dc04338f9ea5f496e354a0a0 Mon Sep 17 00:00:00 2001 From: Stanislav Brabec Date: Wed, 10 Jun 2026 23:57:21 +0200 Subject: [PATCH] netstat: Keep UTF-8 characters in process names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 9beab79 fixed possible ANSI terminal injection. But it also started to sanitize UTF-8 characters in process names making them unreadable. For example (ln -sf /usr/bin/nc /tmp/ncáéí; /tmp/nc* -l 31337 &); netstat -alp was now converted to: nc...... With this change: If LC_CTYPE refers to UTF-8 locale: ncáéí Otherwise: nc?????? Convert special characters to "?" instead of ".". These changes make display names compatible with psmisc ps and iproute2 ss. The escape.c code is based on procps and modified by Stephen Hemminger for iproute2 ss. Reference: https://lore.kernel.org/all/20250204162429.17902-1-stephen@networkplumber.org/ Due to the licensing reasons, the code is kept in a separate file in lib. But it is not added to the library. Reference: https://github.com/ecki/net-tools/issues/57 --- Makefile | 4 +- include/escape.h | 28 ++++++++++++ lib/Makefile | 3 +- lib/escape.c | 111 +++++++++++++++++++++++++++++++++++++++++++++++ netstat.c | 9 +--- 5 files changed, 145 insertions(+), 10 deletions(-) create mode 100644 include/escape.h create mode 100644 lib/escape.c diff --git a/Makefile b/Makefile index 24c138e..a251050 100644 --- a/Makefile +++ b/Makefile @@ -191,8 +191,8 @@ slattach: $(NET_LIB) slattach.o plipconfig: $(NET_LIB) plipconfig.o $(CC) $(CFLAGS) $(LDFLAGS) -o $@ plipconfig.o $(NLIB) $(LDLIBS) -netstat: $(NET_LIB) netstat.o statistics.o - $(CC) $(CFLAGS) $(LDFLAGS) -o $@ netstat.o statistics.o $(NLIB) $(SELIB) $(LDLIBS) +netstat: $(NET_LIB) netstat.o statistics.o lib/escape.o + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ netstat.o statistics.o lib/escape.o $(NLIB) $(SELIB) $(LDLIBS) iptunnel: $(NET_LIB) iptunnel.o $(CC) $(CFLAGS) $(LDFLAGS) -o $@ iptunnel.o $(NLIB) $(LDLIBS) diff --git a/include/escape.h b/include/escape.h new file mode 100644 index 0000000..0238c68 --- /dev/null +++ b/include/escape.h @@ -0,0 +1,28 @@ +/* + * escape.h - printing handling + * + * Copyright © 2011-2023 Jim Warner + * Copyright © 2016-2023 Craig Small + * Copyright © 1998-2005 Albert Cahalan + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef ESCAPE_H +#define ESCAPE_H + +void escape_str_inplace (char *string, int len); + +#endif diff --git a/lib/Makefile b/lib/Makefile index 8347645..90f767f 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -22,6 +22,7 @@ AFGROBJS = inet_gr.o inet6_gr.o ipx_gr.o ddp_gr.o netrom_gr.o ax25_gr.o rose_gr. AFSROBJS = inet_sr.o inet6_sr.o netrom_sr.o ipx_sr.o setroute.o x25_sr.o ACTOBJS = slip_ac.o ppp_ac.o activate.o VARIA = getargs.o masq_info.o proc.o util.o nstrcmp.o interface.o sockets.o +EXTRA = escape.o # Default Name NET_LIB_NAME = net-tools @@ -41,7 +42,7 @@ SONAME=libnet-tools.so.0 .SUFFIXES: .a .so -all: lib$(NET_LIB_NAME).a # lib$(NET_LIB_NAME).so +all: lib$(NET_LIB_NAME).a escape.o # lib$(NET_LIB_NAME).so lib$(NET_LIB_NAME).a: Makefile $(TOPDIR)/config.h $(OBJS) @echo Building $@ diff --git a/lib/escape.c b/lib/escape.c new file mode 100644 index 0000000..1d7e367 --- /dev/null +++ b/lib/escape.c @@ -0,0 +1,111 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Escape character print handling derived from procps + * Copyright 1998-2002 by Albert Cahalan + * Copyright 2020-2022 Jim Warner + * + * Simplification for procps ps: + * Stephen Hemminger , 2025 + * Inplace modification: + * Stanislav Brabec , 2026 + * + */ + +#include +#include +#include +#include + +static const char UTF_tab[] = { + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, // 0x00 - 0x0F + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, // 0x10 - 0x1F + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, // 0x20 - 0x2F + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, // 0x30 - 0x3F + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, // 0x40 - 0x4F + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, // 0x50 - 0x5F + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, // 0x60 - 0x6F + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, // 0x70 - 0x7F + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, // 0x80 - 0x8F + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, // 0x90 - 0x9F + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, // 0xA0 - 0xAF + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, // 0xB0 - 0xBF + -1, -1, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, // 0xC0 - 0xCF + 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, // 0xD0 - 0xDF + 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, // 0xE0 - 0xEF + 4, 4, 4, 4, 4, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, // 0xF0 - 0xFF +}; + +static const unsigned char ESC_tab[] = { + "@..............................." // 0x00 - 0x1F + "||||||||||||||||||||||||||||||||" // 0x20 - 0x3F + "||||||||||||||||||||||||||||||||" // 0x40 - 0x5f + "|||||||||||||||||||||||||||||||." // 0x60 - 0x7F + "????????????????????????????????" // 0x80 - 0x9F + "????????????????????????????????" // 0xA0 - 0xBF + "????????????????????????????????" // 0xC0 - 0xDF + "????????????????????????????????" // 0xE0 - 0xFF +}; + +static void esc_all(unsigned char *str) +{ + // if bad locale/corrupt str, replace non-printing stuff + while (*str) { + unsigned char c = ESC_tab[*str]; + + if (c != '|') + *str = c; + ++str; + } +} + +static void esc_ctl(unsigned char *str, int len) +{ + int i; + + for (i = 0; i < len;) { + // even with a proper locale, strings might be corrupt + int n = UTF_tab[*str]; + + if (n < 0 || i + n > len) { + esc_all(str); + return; + } + // and eliminate those non-printing control characters + if (*str < 0x20 || *str == 0x7f) + *str = '?'; + str += n; + i += n; + } +} + +void escape_str_inplace(char *string, int len) +{ + static int utf_sw; + + if (utf_sw == 0) { + char *enc = nl_langinfo(CODESET); + + utf_sw = enc && strcasecmp(enc, "UTF-8") == 0 ? 1 : -1; + } + + if (utf_sw < 0) + esc_all((unsigned char *)string); + else + esc_ctl((unsigned char *)string, len); +} diff --git a/netstat.c b/netstat.c index b8be850..5a7d97a 100644 --- a/netstat.c +++ b/netstat.c @@ -96,6 +96,7 @@ #include "interface.h" #include "util.h" #include "proc.h" +#include "escape.h" #if HAVE_SELINUX #include @@ -468,13 +469,7 @@ static void prg_cache_load(void) // cmdlbuf[sizeof(cmdlbuf) - 1) is already \0 cmdllen = sizeof(cmdlbuf) - 1; // remove all embedded controls - for(int i=0; i < cmdllen; i++) { - char c = cmdlbuf[i]; - if (c == 0) // we dont process arguments - break; - if (c < ' ' || c > '~') // safe 7bit ASCII - cmdlbuf[i] = '.'; - } + escape_str_inplace (cmdlbuf, strlen(cmdlbuf)); if (cmdlbuf[0] == '/' && (cmdlp = strrchr(cmdlbuf, '/'))) cmdlp++; else