Where
-Infinity
0
Severity
10
Buffer Overflow
AV:N/AC:L/Au:N/C:C/I:C/A:C

Buffer overflow in libtelnet/encrypt.c in telnetd in FreeBSD 7.3 through 9.0, MIT Kerberos Version 5 Applications (aka krb5-appl) 1.0.2 and earlier, Heimdal 1.5.1 and earlier, GNU inetutils, and possibly other products allows remote attackers to execute arbitrary code via a long encryption key, as exploited in the wild in December 2011.

First published (updated )
Severity
9.8
EPSS
76.94%
AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

GNU InetUtils contains an argument injection vulnerability in telnetd that could allow for remote authentication bypass via a "-f root" value for the USER environment variable.

1 / 2
Source: CISA
First published (updated )
Severity
9.8
EPSS
0.03%
AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

Last updated 8 June 2026

1 / 2
Source: Ubuntu
First published (updated )
Severity
7.8
EPSS
0.01%
AV:L/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H

Last updated 8 June 2026

1 / 2
Source: Ubuntu
First published (updated )
Severity
7.8
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

GNU inetutils before 2.5 may allow privilege escalation because of unchecked return values of setid() family functions in ftpd, rcp, rlogin, rsh, rshd, and uucpd. This is, for example, relevant if the setuid system call fails when a process is trying to drop privileges before letting an ordinary user control the activities of the process.

1 / 3
First published (updated )
Severity
7.5
Buffer Overflow
AV:N/AC:L/Au:N/C:P/I:P/A:P

Buffer overflow in the TFTP client in InetUtils 1.4.2 allows remote malicious DNS servers to execute arbitrary code via a large DNS response that is handled by the gethostbyname function.

First published (updated )
Severity
7.5
Null Pointer Dereference
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

telnetd in GNU Inetutils through 2.3, MIT krb5-appl through 1.0.3, and derivative works has a NULL pointer dereference via 0xff 0xf7 or 0xff 0xf8. In a typical installation, the telnetd application would crash but the telnet service would remain available through inetd. However, if the telnetd application has many crashes within a short time interval, the telnet service would become unavailable after inetd logs a "telnet/tcp server failing (looping), service terminated" error. NOTE: MIT krb5-appl is not supported upstream but is shipped by a few Linux distributions. The affected code was removed from the supported MIT Kerberos 5 (aka krb5) product many years ago, at version 1.8.

1 / 2
First published (updated )
Severity
6.5
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N

The ftp client in GNU Inetutils before 2.2 does not validate addresses returned by PASV/LSPV responses to make sure they match the server address. This is similar to CVE-2020-8284 for curl.

First published (updated )
Severity
4.7
EPSS
0.03%
AV:N/AC:H/PR:N/UI:R/S:C/C:L/I:N/A:N

Last updated 8 June 2026

1 / 2
Source: Ubuntu
First published (updated )

If you are tired of modern age vulnerabilities, and remember the good old times on bugtraq, I hope you will appreciate this one. If someone can allocated a CVE, we will add it in future release notes.

/Simon

GNU InetUtils Security Advisory: remote authentication by-pass in telnetd

The telnetd server invokes /usr/bin/login (normally running as root) passing the value of the USER environment variable received from the client as the last parameter.

If the client supply a carefully crafted USER environment value being the string "-f root", and passes the telnet(1) -a or --login parameter to send this USER environment to the server, the client will be automatically logged in as root bypassing normal authentication processes.

This happens because the telnetd server do not sanitize the USER environment variable before passing it on to login(1), and login(1) uses the -f parameter to by-pass normal authentication.

Severity: High

Vulnerable versions: GNU InetUtils since version 1.9.3 up to and including version 2.7.

Example

On a Trisquel GNU/Linux 11 aramo laptop:

root@kaka:~ sudo apt-get install inetutils-telnetd telnet root@kaka:~ sudo sed -i 's/#<off># telnet/telnet/' /etc/inetd.conf root@kaka:~ sudo /etc/init.d/inetutils-inetd start root@kaka:~ USER='-f root' telnet -a localhost ... root@kaka:~#

History

The bug was introduced in the following commit made on 2015 March 19:

https://codeberg.org/inetutils/inetutils/commit/fa3245ac8c288b87139a0da8249d0a408c4dfb87

Based on mailing list discussions:

https://lists.gnu.org/archive/html/bug-inetutils/2014-12/msg00012.html https://lists.gnu.org/archive/html/bug-inetutils/2015-03/msg00001.html

It was included in the v1.9.3 release made on 2015 May 12.

Recommendation

Do not run a telnetd server at all. Restrict network access to the telnet port to trusted clients.

Apply the patch or upgrade to a newer release which incorporate the patch.

Workaround

Disable telnetd server or make the InetUtils telnetd use a custom login(1) tool that does not permit use of the '-f' parameter.

Further research

The template for invoking login(1) is in telnetd/telnetd.c:

/ Template command line for invoking login program. / char logininvocation = #ifdef SOLARIS10 / TODO: -s telnet' or -s ktelnet'. -u' takes the Kerberos principal name of the authenticating, remote user. / PATHLOGIN " -p -h %h %?T{-t %T} -d %L %?u{-u %u}{%U}" #elif defined SOLARIS / At least for SunOS 5.8. / PATHLOGIN " -h %h %?T{%T} %?u{-- %u}{%U}" #else / !SOLARIS / PATHLOGIN " -p -h %h %?u{-f %u}{%U}" #endif ;

The variable expansion happens in telnetd/utility.c:

/ Expand a variable referenced by its short one-symbol name. Input: exp->cp points to the variable name. FIXME: not implemented / char varshortname (struct lineexpander exp) { char q; char timebuf[64]; timet t; switch (exp->cp++) { case 'a': #ifdef AUTHENTICATION if (authlevel >= 0 && autologin == AUTHVALID) return xstrdup ("ok"); #endif return NULL; case 'd': time (&t); strftime (timebuf, sizeof (timebuf), "%l:%M%p on %A, %d %B %Y", localtime (&t)); return xstrdup (timebuf); case 'h': return xstrdup (remotehostname); case 'l': return xstrdup (localhostname); case 'L': return xstrdup (line); case 't': q = strchr (line + 1, '/'); if (q) q++; else q = line; return xstrdup (q); case 'T': return terminaltype ? xstrdup (terminaltype) : NULL; case 'u': return username ? xstrdup (username) : NULL; case 'U': return getenv ("USER") ? xstrdup (getenv ("USER")) : xstrdup (""); default: exp->state = EXPSTATEERROR; return NULL; } }

Thus there is potential for similar vulnerabilities for other variables.

On non-GNU/Linux systems, only the remote hostname field is of interest. The remotehostname variable is populated in the function telnetdsetup from telnetd/telnetd.c by calling getnameinfo() or gethostbyaddr() depending on platform. This API is generally not considered to return trusted data, thus relying on it to not return a value such as 'foo -f root' is not advisable.

Patch

We chose to sanitize all variables for expansion. The following two patches are what we suggest:

https://codeberg.org/inetutils/inetutils/commit/fd702c02497b2f398e739e3119bed0b23dd7aa7b https://codeberg.org/inetutils/inetutils/commit/ccba9f748aa8d50a38d7748e2e60362edd6a32cc

Credits

This vulnerability was found and reported by Kyu Neushwaistein aka Carlos Cortes Alvarez on 2026-01-19.

Initial patch by Paul Eggert on 2026-01-20. Simon Josefsson improved the patch to also cover similar concerns with other expansions.

This advisory was drafted by Simon Josefsson on 2026-01-20.

First published (updated )

...on 2026-01-20 15:00:07, Simon Josefsson wrote: Vulnerable versions: GNU InetUtils since version 1.9.3 up to and including version 2.7. Looking at Debian, this gets even more hilarious... Their changelog for inetutils has: inetutils (2:1.9.4-7) unstable; urgency=medium [..] Take several patches from upstream git master: [..] - 0028-telnetd-Scrub-USER-from-environment.patch

[..] Sat, 16 Feb 2019 18:09:37 +0100 I have not yet spun up a Debian 9 to see if that version was released as an update, but it presumably would have been safe in this regard.

The next entry in their changelog is for Debian 10, inetutils (2:1.9.4-7+deb10u1) buster; urgency=medium

CVE-2020-10188 (Closes: #956084)

[..] Fri, 18 Sep 2020 20:06:42 +0200 That update fixed a remote code execution in telnetd and apparently reintroduced the environment bug yet another time (I tested that Debian 10 telnetd is vulnerable for this and later versions, and also subsequent Debian and Ubuntu releases)...

Alex.

First published (updated )

Alexander Bochmann <ab () lists gxis de>, 2026-01-21 00:16: ...on 2026-01-20 15:00:07, Simon Josefsson wrote: Looking at Debian, this gets even more hilarious... Their changelog for inetutils has: inetutils (2:1.9.4-7) unstable; urgency=medium [..] Take several patches from upstream git master: [..] - 0028-telnetd-Scrub-USER-from-environment.patch I think this is unrelated.

The bug is reproducible with inetutils-telnetd 2:1.9.4-7 too.

-- Jakub Wilk

First published (updated )

On 1/20/26 09:00, Simon Josefsson wrote: We chose to sanitize all variables for expansion. The following two patches are what we suggest:

https://codeberg.org/inetutils/inetutils/commit/fd702c02497b2f398e739e3119bed0b23dd7aa7b https://codeberg.org/inetutils/inetutils/commit/ccba9f748aa8d50a38d7748e2e60362edd6a32cc If a variable expands to an empty value, will the subsequent code remove the command-line argument entirely, rather than passing an empty string? Or should an empty string be treated as an error?

Also, would an allowlist be better than a denylist? -- Sincerely, Demi Marie Obenour (she/her/hers)

USER='-f root' telnet -a localhost Seems that the same code path results from the even simpler incantation:

$ telnet -l 'root -f' server.test

The user-name-with-a-space-in-it doesn't get passed as a single argument to execv(), but "helpfully" gets split back into two parts and passed to execv() as two separate entries in argv[] :-)

Q. "Hey, if we call execv() directly, we'll avoid some of the risks associated with shell-style command line processing. How good is that?"

A. "Hold my beer."

TELNET, eh? From the days when RFCs still had just three digits...

Greetings,

I have been reviewing the recent vulnerability report by Ron Ben Yizhak regarding CREDENTIALSDIRECTORY, as well as commit 4db2f19f which introduces unsetenv("CREDENTIALSDIRECTORY") to address the problem.

After becoming aware of CVE-2026-24061 (telnetd in GNU Inetutils through 2.7 allows remote authentication bypass via a "-f root" value for the USER environment variable), I was curious to find out whether there'd also been a potential regression of CVE-1999-0073, described as: telnet allows a remote client to specify environment variables including LDLIBRARYPATH, allowing an attacker to bypass the normal system libraries and gain root access. I can confirm that this is still an issue 27 years later, despite attempts at blacklisting environment variables by prefix or full name.

The problem stems from telnetd executing /bin/login in a root-to-root context, which means that ATSECURE is set to 0 by the kernel in the process's auxiliary vector. When ATSECURE holds a positive value, it informs the dynamic linker (ld-linux.so) and libc to enter a "secure-execution mode" where a bunch of interesting environment variables are discarded or, at least, defanged if present. In other words, the responsibility is on telnetd itself to ensure that none of those potentially interesting, and attacker controlled, variables make their way to /bin/login.

While using unsetenv() negates a user's ability to exploit the login.noauth vector, the possibility still exists for the inclusion of variables of interest to GNU gettext (such as OUTPUTCHARSET or LANGUAGE) and glibc (such as GCONVPATH) via the telnet protocol itself.

For example, by injecting OUTPUTCHARSET and LANGUAGE, an attacker can persuade gettext that a character set conversion is necessary. This forces gettext to call libc's iconvopen(), and because ATSECURE is 0, iconvopen() will use an injected GCONVPATH in its quest for a gconv-modules file. Assuming the attacker already has a local unprivileged account, or at least a means of uploading files to the host (and knowing the location of the uploaded files), a custom gconv-modules file will allow arbitrary shared objects to be loaded soon after /bin/login attempts to print a localized prompt.

For proof of concept, I've declared a broad selection of LANGUAGE codes for the best chance of matching an installed locale. An attacker with local access could simply determine what's actually installed and select only one that doesn't match the system's default locale instead. Similarly, OUTPUTCHARSET has been chosen as a deliberate mismatch against the very common choice of UTF-8:

abuser@prospecton.hyperama:~$ ls -al .gconv total 184 drwxr-xr-x 2 abuser abuser 4096 Jan 1 1970 . drwxr-x--- 5 abuser abuser 36864 Jan 1 1970 .. -rw-r--r-- 1 abuser abuser 256 Jan 1 1970 gconv-modules -rw-r--r-- 1 abuser abuser 15568 Jan 1 1970 libcash2trash.so

abuser@prospecton.hyperama:~$ telnet -l abuser telnet> environ define GCONVPATH /home/abuser/.gconv telnet> environ export GCONVPATH telnet> environ define LANGUAGE fr:de:es:it:pt:nl:sv:pl:uk:ru:zhCN:ko:ja telnet> environ export LANGUAGE telnet> environ define OUTPUTCHARSET ISO-8859-1 telnet> environ export OUTPUTCHARSET telnet> open 127.0.0.1 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]'. Linux (localhost) (pts/6) Connection closed by foreign host.

abuser@prospecton.hyperama:~$ ls -al .gconv total 184 drwxr-xr-x 2 abuser abuser 4096 Jan 1 1970 . drwxr-x--- 5 abuser abuser 36864 Jan 1 1970 .. -rw-r--r-- 1 abuser abuser 256 Jan 1 1970 gconv-modules -rw-r--r-- 1 abuser abuser 15568 Jan 1 1970 libcash2trash.so -rwsr-sr-x 1 root root 125640 Jan 1 1970 trash

abuser@prospecton.hyperama:~$ .gconv/trash -p # id uid=1001(abuser) gid=1002(abuser) euid=0(root) egid=0(root) groups=0(root),1002(abuser)

Once the telnet connection opens, /bin/login tries to print the localized prompt but gettext recognizes the encoding mismatch and calls iconvopen() to parse the gconv-modules file in the directory referenced by the injected path before loading the shared object that turns cash ($) to trash (#). The connection drops because I included a call to exit() once the payload has executed. As illustrated above, the payload effectively asserts root privilege and makes a copy of /bin/sh with SUID/SGID permissions. Note that no authentication via telnetd was required, nor performed, for this privilege escalation trick to occur. Also note that this is just one of many possible methods that may be used to exploit this condition.

In my opinion, to fix this issue and finally put the ghost of CVE-1999-0073 to rest: telnetd must drop the blacklist approach and adopt the OpenSSH AcceptEnv-style approach suggested by Simon Josefsson [1], which amounts to preparing a brand new environment for /bin/login based on a strict whitelist of variables names considered to be "safe", and perhaps a healthy dose of input sanitization for their respective values.

In terms of the CVE that Ron Ben Yizhak had asked about earlier in the thread: I think it might make the most sense to co-ordinate a single CVE for "Improper environment sanitization in telnetd" that comprehensively covers both the CREDENTIALSDIRECTORY vector and this dynamic linker escape.

I'm happy to share the intentionally redacted payload privately with the maintainers should any help be required to reproduce the proof of concept.

Regards, Justin

---

[1] https://lists.gnu.org/archive/html/bug-inetutils/2026-02/msg00002.html

Hi,

Thank you for bringing this to oss-security, Justin!

On Tue, Feb 24, 2026 at 03:17:02AM +0200, Justin Swartz wrote: I have been reviewing the recent vulnerability report by Ron Ben Yizhak regarding CREDENTIALSDIRECTORY, as well as commit 4db2f19f which introduces unsetenv("CREDENTIALSDIRECTORY") to address the problem. Looks like this wasn't reported to oss-security yet, so let me do that now. Ron Ben Yizhak's report is here:

https://lists.gnu.org/archive/html/bug-inetutils/2026-02/msg00000.html

and I quote it in full below: From: Ron Ben Yizhak Subject: Telnetd Vulnerability Report Date: Thu, 5 Feb 2026 14:39:57 +0200

Hello,

My name is Ron Ben Yizhak and I am a security researcher from SafeBreach.

I want to report a severe vulnerability that I found in telnetd from the repository https://codeberg.org/inetutils/inetutils

After the vulnerability CVE-2026-24061 was fixed, it was no longer possible to force telnetd to execute /usr/bin/login with the parameter -f and skip the authentication. However the research on CVE-2026-24061 revealed that telnetd allows clients to set environment variables for the telnetd process itself and for all its sub processes. One of its processes as we know from CVE-2026-24061, is /usr/bin/login.

This is the root cause of the vulnerability I’m reporting to you. Setting environment variables as a remote telnet client enables us to spawn /usr/bin/login with the environment variable “CREDENTIALSDIRECTORY”. This will make the process search for a file named “login.noauth” in the directory specified by “CREDENTIALSDIRECTORY”. If the file contains the string “yes”, login will skip the authentication and provide a shell for the client running as the user the client specified. It can be any user, even root.

https://github.com/util-linux/util-linux/blob/master/login-utils/login.c#L1306

Attached to this mail is a video demonstrating the vulnerability on a fully patched ubuntu 25 machine with the latest telnetd. Also attached is a script to test this vulnerability

Usage:

1. create a directory as a low privileged user, for example /home/weakuser/fakecred

2. create a file named login.noauth containing the string “yes” inside this directory

3. launch the python script with the following parameters

./telnetlpe.py 127.0.0.1 --env CREDENTIALSDIRECTORY=/home/weakuser/fakecred --env USER=root

Best regards, Ron Ben Yizhak

Attachment: telnetlpe.py Description: Text Data

Attachment: telnetlpedemo.mp4 Description: video/mp4 I'm re-attaching telnetlpe.py, but not the video.

On Tue, Feb 24, 2026 at 03:17:02AM +0200, Justin Swartz wrote: After becoming aware of CVE-2026-24061 (telnetd in GNU Inetutils through 2.7 allows remote authentication bypass via a "-f root" value for the USER environment variable), I was curious to find out whether there'd also been a potential regression of CVE-1999-0073, described as: telnet allows a remote client to specify environment variables including LDLIBRARYPATH, allowing an attacker to bypass the normal system libraries and gain root access. I can confirm that this is still an issue 27 years later, despite attempts at blacklisting environment variables by prefix or full name.

The problem stems from telnetd executing /bin/login in a root-to-root context, which means that ATSECURE is set to 0 by the kernel in the process's auxiliary vector. When ATSECURE holds a positive value, it informs the dynamic linker (ld-linux.so) and libc to enter a "secure-execution mode" where a bunch of interesting environment variables are discarded or, at least, defanged if present. In other words, the responsibility is on telnetd itself to ensure that none of those potentially interesting, and attacker controlled, variables make their way to /bin/login.

While using unsetenv() negates a user's ability to exploit the login.noauth vector, the possibility still exists for the inclusion of variables of interest to GNU gettext (such as OUTPUTCHARSET or LANGUAGE) and glibc (such as GCONVPATH) via the telnet protocol itself.

For example, by injecting OUTPUTCHARSET and LANGUAGE, an attacker can persuade gettext that a character set conversion is necessary. This forces gettext to call libc's iconvopen(), and because ATSECURE is 0, iconvopen() will use an injected GCONVPATH in its quest for a gconv-modules file. Assuming the attacker already has a local unprivileged account, or at least a means of uploading files to the host (and knowing the location of the uploaded files), a custom gconv-modules file will allow arbitrary shared objects to be loaded soon after /bin/login attempts to print a localized prompt.

For proof of concept, I've declared a broad selection of LANGUAGE codes for the best chance of matching an installed locale. An attacker with local access could simply determine what's actually installed and select only one that doesn't match the system's default locale instead. Similarly, OUTPUTCHARSET has been chosen as a deliberate mismatch against the very common choice of UTF-8:

abuser@prospecton.hyperama:~$ ls -al .gconv total 184 drwxr-xr-x 2 abuser abuser 4096 Jan 1 1970 . drwxr-x--- 5 abuser abuser 36864 Jan 1 1970 .. -rw-r--r-- 1 abuser abuser 256 Jan 1 1970 gconv-modules -rw-r--r-- 1 abuser abuser 15568 Jan 1 1970 libcash2trash.so

abuser@prospecton.hyperama:~$ telnet -l abuser telnet> environ define GCONVPATH /home/abuser/.gconv telnet> environ export GCONVPATH telnet> environ define LANGUAGE fr:de:es:it:pt:nl:sv:pl:uk:ru:zhCN:ko:ja telnet> environ export LANGUAGE telnet> environ define OUTPUTCHARSET ISO-8859-1 telnet> environ export OUTPUTCHARSET telnet> open 127.0.0.1 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]'. Linux (localhost) (pts/6) Connection closed by foreign host.

abuser@prospecton.hyperama:~$ ls -al .gconv total 184 drwxr-xr-x 2 abuser abuser 4096 Jan 1 1970 . drwxr-x--- 5 abuser abuser 36864 Jan 1 1970 .. -rw-r--r-- 1 abuser abuser 256 Jan 1 1970 gconv-modules -rw-r--r-- 1 abuser abuser 15568 Jan 1 1970 libcash2trash.so -rwsr-sr-x 1 root root 125640 Jan 1 1970 trash

abuser@prospecton.hyperama:~$ .gconv/trash -p # id uid=1001(abuser) gid=1002(abuser) euid=0(root) egid=0(root) groups=0(root),1002(abuser)

Once the telnet connection opens, /bin/login tries to print the localized prompt but gettext recognizes the encoding mismatch and calls iconvopen() to parse the gconv-modules file in the directory referenced by the injected path before loading the shared object that turns cash ($) to trash (#). The connection drops because I included a call to exit() once the payload has executed. As illustrated above, the payload effectively asserts root privilege and makes a copy of /bin/sh with SUID/SGID permissions. Note that no authentication via telnetd was required, nor performed, for this privilege escalation trick to occur. Also note that this is just one of many possible methods that may be used to exploit this condition.

In my opinion, to fix this issue and finally put the ghost of CVE-1999-0073 to rest: telnetd must drop the blacklist approach and adopt the OpenSSH AcceptEnv-style approach suggested by Simon Josefsson [1], which amounts to preparing a brand new environment for /bin/login based on a strict whitelist of variables names considered to be "safe", and perhaps a healthy dose of input sanitization for their respective values. Oh, sure. A couple of decades ago I ported OpenBSD's telnet and telnetd to Linux for our distro, Owl. I no longer recalled all detail, but looking at my "Linux port" patch now, it appears to implement a strict allow-list approach already. There's a comment saying the "list comes from Linux NetKit telnetd, version 0.17", so maybe NetKit already used that approach too, and Linux distros got a regression by switching from NetKit to InetUtils? Or it could be that Red Hat used NetKit and Debian went with InetUtils. I see I'm also lightly sanitizing env var values (only for not containing '/' and being of sane length), which I doubt was in NetKit.

My very first RPM changelog entry was:

Sat Nov 17 2001 Solar Designer <solar-at-owl.openwall.com> - Ported the telnet client and server from OpenBSD-current (post-3.0), reviewing changes made in NetBSD-current, FreeBSD-current, and Linux NetKit 0.17. - Filter environment variables in telnetd with a white list (took the list itself from NetKit), but also use a black list for logging likely attacks. - Dropped the "mini inetd" from telnetd. - Dropped Kerberos-related pieces from the man pages (the telnet stuff is already bad enough, let's better not add to that). - Wrote telnetd.xinetd. - Wrote this spec file, based (sub)package descriptions on Red Hat's.

So at least OpenBSD was at risk, relying on their badenvtable being comprehensive. Probably other BSDs too.

Perhaps non-Linux systems that still have telnetd also still have this weakness, and you could find specific env vars/values that would work for them?

There was also a symmetric problem where a malicious server could request arbitrary env vars from the telnet client, obtaining sensitive information. This too was promptly fixed in my port:

Wed Nov 21 2001 Solar Designer <solar-at-owl.openwall.com> [...] - Added a Red Hat Linux derived patch to the telnet client such that it permits queries for exported variables only.

... so apparently it had been fixed in Red Hat Linux, but not BSDs.

Further, I implemented privsep:

Sun Nov 25 2001 Solar Designer <solar-at-owl.openwall.com> - Do telnet protocol handling as a dedicated pseudo-user and in a chroot jail. This uses the approach introduced by Chris Evans in his NetKit telnetd patches, but the code is different.

Architecture diagram:

https://www.openwall.com/presentations/Owl/mgp00017.html

Patches against OpenBSD telnet/telnetd:

https://github.com/openwall/Owl/tree/main/packages/telnet

Tarball with the OpenBSD code to be patched:

https://download.openwall.net/pub/Owl/pool/sources/telnet/

This can be revitalized and used to replace the code currently in InetUtils if anyone is willing to invest significant time into that. In terms of the CVE that Ron Ben Yizhak had asked about earlier in the thread: I think it might make the most sense to co-ordinate a single CVE for "Improper environment sanitization in telnetd" that comprehensively covers both the CREDENTIALSDIRECTORY vector and this dynamic linker escape. So essentially a new CVE for improper/incomplete fix of CVE-1999-0073, emphasizing that only an allow list is considered a proper fix of the new CVE. Makes sense to me. I'm happy to share the intentionally redacted payload privately with the maintainers should any help be required to reproduce the proof of concept.

Regards, Justin

---

[1] https://lists.gnu.org/archive/html/bug-inetutils/2026-02/msg00002.html Thank you both for taking a fresh look and sharing these findings.

Alexander

Hi all,

I’d like to ensure we follow the standard CVE process here. Standard practice dictates that a CVE is issued per individual fix. Generally, once a fix is merged and released, it is assigned its own CVE. Even if that fix is later bypassed, the original merge stands as a unique event in the codebase, meaning we should issue two separate CVEs rather than grouping them.

Justin - Thank you for emphasizing what I already noted previously in this thread:

"In my opinion the proposed fix will stop this exploit, but the main issue stays. The issue exists as long as unauthenticated clients can set arbitrary environment variables in the memory of telnetd and its sub processes. The best solution will be that the environment variables set by the client will only apply on the shell process and only after the client has already authenticated. No process running as root should run with any environment variables set by the client."

In spite of this opinion, it seems that the decision of the developers was to first release something quick that will as least start by mitigating the specific exploit that I shared, even though the main issue remains. I guess that a concrete exploit that utilizes different environment variables might change the prioritization for implementing an "AcceptEnv-like" logic in GNU telnetd, but they might choose to just unset the environment variables that you find as exploitable.

Best regards, Ron Ben Yizhak

On Tue, Feb 24, 2026 at 3:16 AM Justin Swartz < justin.swartz () risingedge co za> wrote: Greetings,

I have been reviewing the recent vulnerability report by Ron Ben Yizhak regarding CREDENTIALSDIRECTORY, as well as commit 4db2f19f which introduces unsetenv("CREDENTIALSDIRECTORY") to address the problem.

After becoming aware of CVE-2026-24061 (telnetd in GNU Inetutils through 2.7 allows remote authentication bypass via a "-f root" value for the USER environment variable), I was curious to find out whether there'd also been a potential regression of CVE-1999-0073, described as: telnet allows a remote client to specify environment variables including LDLIBRARYPATH, allowing an attacker to bypass the normal system libraries and gain root access. I can confirm that this is still an issue 27 years later, despite attempts at blacklisting environment variables by prefix or full name.

The problem stems from telnetd executing /bin/login in a root-to-root context, which means that ATSECURE is set to 0 by the kernel in the process's auxiliary vector. When ATSECURE holds a positive value, it informs the dynamic linker (ld-linux.so) and libc to enter a "secure-execution mode" where a bunch of interesting environment variables are discarded or, at least, defanged if present. In other words, the responsibility is on telnetd itself to ensure that none of those potentially interesting, and attacker controlled, variables make their way to /bin/login.

While using unsetenv() negates a user's ability to exploit the login.noauth vector, the possibility still exists for the inclusion of variables of interest to GNU gettext (such as OUTPUTCHARSET or LANGUAGE) and glibc (such as GCONVPATH) via the telnet protocol itself.

For example, by injecting OUTPUTCHARSET and LANGUAGE, an attacker can persuade gettext that a character set conversion is necessary. This forces gettext to call libc's iconvopen(), and because ATSECURE is 0, iconvopen() will use an injected GCONVPATH in its quest for a gconv-modules file. Assuming the attacker already has a local unprivileged account, or at least a means of uploading files to the host (and knowing the location of the uploaded files), a custom gconv-modules file will allow arbitrary shared objects to be loaded soon after /bin/login attempts to print a localized prompt.

For proof of concept, I've declared a broad selection of LANGUAGE codes for the best chance of matching an installed locale. An attacker with local access could simply determine what's actually installed and select only one that doesn't match the system's default locale instead. Similarly, OUTPUTCHARSET has been chosen as a deliberate mismatch against the very common choice of UTF-8:

abuser@prospecton.hyperama:~$ ls -al .gconv total 184 drwxr-xr-x 2 abuser abuser 4096 Jan 1 1970 . drwxr-x--- 5 abuser abuser 36864 Jan 1 1970 .. -rw-r--r-- 1 abuser abuser 256 Jan 1 1970 gconv-modules -rw-r--r-- 1 abuser abuser 15568 Jan 1 1970 libcash2trash.so

abuser@prospecton.hyperama:~$ telnet -l abuser telnet> environ define GCONVPATH /home/abuser/.gconv telnet> environ export GCONVPATH telnet> environ define LANGUAGE fr:de:es:it:pt:nl:sv:pl:uk:ru:zhCN:ko:ja telnet> environ export LANGUAGE telnet> environ define OUTPUTCHARSET ISO-8859-1 telnet> environ export OUTPUTCHARSET telnet> open 127.0.0.1 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]'.

Linux (localhost) (pts/6)

Connection closed by foreign host.

abuser@prospecton.hyperama:~$ ls -al .gconv total 184 drwxr-xr-x 2 abuser abuser 4096 Jan 1 1970 . drwxr-x--- 5 abuser abuser 36864 Jan 1 1970 .. -rw-r--r-- 1 abuser abuser 256 Jan 1 1970 gconv-modules -rw-r--r-- 1 abuser abuser 15568 Jan 1 1970 libcash2trash.so -rwsr-sr-x 1 root root 125640 Jan 1 1970 trash

abuser@prospecton.hyperama:~$ .gconv/trash -p # id uid=1001(abuser) gid=1002(abuser) euid=0(root) egid=0(root) groups=0(root),1002(abuser)

Once the telnet connection opens, /bin/login tries to print the localized prompt but gettext recognizes the encoding mismatch and calls iconvopen() to parse the gconv-modules file in the directory referenced by the injected path before loading the shared object that turns cash ($) to trash (#). The connection drops because I included a call to exit() once the payload has executed. As illustrated above, the payload effectively asserts root privilege and makes a copy of /bin/sh with SUID/SGID permissions. Note that no authentication via telnetd was required, nor performed, for this privilege escalation trick to occur. Also note that this is just one of many possible methods that may be used to exploit this condition.

In my opinion, to fix this issue and finally put the ghost of CVE-1999-0073 to rest: telnetd must drop the blacklist approach and adopt the OpenSSH AcceptEnv-style approach suggested by Simon Josefsson [1], which amounts to preparing a brand new environment for /bin/login based on a strict whitelist of variables names considered to be "safe", and perhaps a healthy dose of input sanitization for their respective values.

In terms of the CVE that Ron Ben Yizhak had asked about earlier in the thread: I think it might make the most sense to co-ordinate a single CVE for "Improper environment sanitization in telnetd" that comprehensively covers both the CREDENTIALSDIRECTORY vector and this dynamic linker escape.

I'm happy to share the intentionally redacted payload privately with the maintainers should any help be required to reproduce the proof of concept.

Regards, Justin

---

[1] https://lists.gnu.org/archive/html/bug-inetutils/2026-02/msg00002.html

Who uses telnet anyway? It's deprecated. Everyone uses ssh for any kind of remote access.

On Tuesday, February 24th, 2026 at 7:51 AM, Justin Swartz <justin.swartzatrisingedge.co.zakf503bla () duck com> wrote: Greetings,

I have been reviewing the recent vulnerability report by Ron Ben Yizhak regarding CREDENTIALSDIRECTORY, as well as commit 4db2f19f which introduces unsetenv("CREDENTIALSDIRECTORY") to address the problem.

After becoming aware of CVE-2026-24061 (telnetd in GNU Inetutils through 2.7 allows remote authentication bypass via a "-f root" value for the USER environment variable), I was curious to find out whether there'd also been a potential regression of CVE-1999-0073, described as: telnet allows a remote client to specify environment variables including LDLIBRARYPATH, allowing an attacker to bypass the normal system libraries and gain root access. I can confirm that this is still an issue 27 years later, despite attempts at blacklisting environment variables by prefix or full name.

The problem stems from telnetd executing /bin/login in a root-to-root context, which means that ATSECURE is set to 0 by the kernel in the process's auxiliary vector. When ATSECURE holds a positive value, it informs the dynamic linker (ld-linux.so) and libc to enter a "secure-execution mode" where a bunch of interesting environment variables are discarded or, at least, defanged if present. In other words, the responsibility is on telnetd itself to ensure that none of those potentially interesting, and attacker controlled, variables make their way to /bin/login.

While using unsetenv() negates a user's ability to exploit the login.noauth vector, the possibility still exists for the inclusion of variables of interest to GNU gettext (such as OUTPUTCHARSET or LANGUAGE) and glibc (such as GCONVPATH) via the telnet protocol itself.

For example, by injecting OUTPUTCHARSET and LANGUAGE, an attacker can persuade gettext that a character set conversion is necessary. This forces gettext to call libc's iconvopen(), and because ATSECURE is 0, iconvopen() will use an injected GCONVPATH in its quest for a gconv-modules file. Assuming the attacker already has a local unprivileged account, or at least a means of uploading files to the host (and knowing the location of the uploaded files), a custom gconv-modules file will allow arbitrary shared objects to be loaded soon after /bin/login attempts to print a localized prompt.

For proof of concept, I've declared a broad selection of LANGUAGE codes for the best chance of matching an installed locale. An attacker with local access could simply determine what's actually installed and select only one that doesn't match the system's default locale instead. Similarly, OUTPUTCHARSET has been chosen as a deliberate mismatch against the very common choice of UTF-8:

abuser@prospecton.hyperama:~$ ls -al .gconv total 184 drwxr-xr-x 2 abuser abuser 4096 Jan 1 1970 . drwxr-x--- 5 abuser abuser 36864 Jan 1 1970 .. -rw-r--r-- 1 abuser abuser 256 Jan 1 1970 gconv-modules -rw-r--r-- 1 abuser abuser 15568 Jan 1 1970 libcash2trash.so

abuser@prospecton.hyperama:~$ telnet -l abuser telnet> environ define GCONVPATH /home/abuser/.gconv telnet> environ export GCONVPATH telnet> environ define LANGUAGE fr:de:es:it:pt:nl:sv:pl:uk:ru:zhCN:ko:ja telnet> environ export LANGUAGE telnet> environ define OUTPUTCHARSET ISO-8859-1 telnet> environ export OUTPUTCHARSET telnet> open 127.0.0.1 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]'.

Linux (localhost) (pts/6)

Connection closed by foreign host.

abuser@prospecton.hyperama:~$ ls -al .gconv total 184 drwxr-xr-x 2 abuser abuser 4096 Jan 1 1970 . drwxr-x--- 5 abuser abuser 36864 Jan 1 1970 .. -rw-r--r-- 1 abuser abuser 256 Jan 1 1970 gconv-modules -rw-r--r-- 1 abuser abuser 15568 Jan 1 1970 libcash2trash.so -rwsr-sr-x 1 root root 125640 Jan 1 1970 trash

abuser@prospecton.hyperama:~$ .gconv/trash -p # id uid=1001(abuser) gid=1002(abuser) euid=0(root) egid=0(root) groups=0(root),1002(abuser)

Once the telnet connection opens, /bin/login tries to print the localized prompt but gettext recognizes the encoding mismatch and calls iconvopen() to parse the gconv-modules file in the directory referenced by the injected path before loading the shared object that turns cash ($) to trash (#). The connection drops because I included a call to exit() once the payload has executed. As illustrated above, the payload effectively asserts root privilege and makes a copy of /bin/sh with SUID/SGID permissions. Note that no authentication via telnetd was required, nor performed, for this privilege escalation trick to occur. Also note that this is just one of many possible methods that may be used to exploit this condition.

In my opinion, to fix this issue and finally put the ghost of CVE-1999-0073 to rest: telnetd must drop the blacklist approach and adopt the OpenSSH AcceptEnv-style approach suggested by Simon Josefsson [1], which amounts to preparing a brand new environment for /bin/login based on a strict whitelist of variables names considered to be "safe", and perhaps a healthy dose of input sanitization for their respective values.

In terms of the CVE that Ron Ben Yizhak had asked about earlier in the thread: I think it might make the most sense to co-ordinate a single CVE for "Improper environment sanitization in telnetd" that comprehensively covers both the CREDENTIALSDIRECTORY vector and this dynamic linker escape.

I'm happy to share the intentionally redacted payload privately with the maintainers should any help be required to reproduce the proof of concept.

Regards, Justin

---

[1] https://lists.gnu.org/archive/html/bug-inetutils/2026-02/msg00002.html

Hi Guillem and Salvatore,

On Fri, Feb 27, 2026 at 01:09:57PM +0100, Guillem Jover wrote: On Tue, 2026-02-24 at 11:57:34 +0200, Ron Ben Yizhak wrote: I’d like to ensure we follow the standard CVE process here. Standard practice dictates that a CVE is issued per individual fix. Generally, once a fix is merged and released, it is assigned its own CVE. Even if that fix is later bypassed, the original merge stands as a unique event in the codebase, meaning we should issue two separate CVEs rather than grouping them. Salvatore Bonaccorso from the Debian Security Team got a CVE assigned for this, see <https://www.cve.org/CVERecord?id=CVE-2026-28372>. I'll update the Debian packaging on the next upload to point to that. The CVE description says:

"telnetd in GNU inetutils through 2.7 allows privilege escalation that can be exploited by abusing systemd service credentials support added to the login(1) implementation of util-linux in release 2.40. This is related to client control over the CREDENTIALSDIRECTORY environment variable, and requires an unprivileged local user to create a login.noauth file."

So is this CVE only for the attack vector reported by Ron Ben Yizhak, and not also for the other attack vector and more general issue reported by Justin Swartz?

If so, are you going to assign a second CVE for the more general issue?

I am not convinced "the standard CVE process" is exactly as Ron Ben Yizhak describes it above, but I don't mind doing things in this way.

It sometimes happens that a fix is released as being for a certain CVE, and then a second CVE has to be assigned for the "incomplete fix", where the incompleteness of the first fix is the new vulnerability. But with no CVE assigned yet, we didn't have to do it this way. We could have one CVE for the set of issues, and not treat "the original merge" as fixing any CVE at all.

But again, I don't mind, and I understand that we also need to enable researchers to find and report such issues during work hours, which means making employers happy with credits and CVEs. It isn't wrong to have separate CVEs, so we may.

Just need to clarify.

Alexander

Hi!

On Fri, 2026-03-06 at 16:16:49 +0100, Solar Designer wrote: On Fri, Feb 27, 2026 at 01:09:57PM +0100, Guillem Jover wrote: On Tue, 2026-02-24 at 11:57:34 +0200, Ron Ben Yizhak wrote: I’d like to ensure we follow the standard CVE process here. Standard practice dictates that a CVE is issued per individual fix. Generally, once a fix is merged and released, it is assigned its own CVE. Even if that fix is later bypassed, the original merge stands as a unique event in the codebase, meaning we should issue two separate CVEs rather than grouping them. Salvatore Bonaccorso from the Debian Security Team got a CVE assigned for this, see <https://www.cve.org/CVERecord?id=CVE-2026-28372>. I'll update the Debian packaging on the next upload to point to that. The CVE description says:

"telnetd in GNU inetutils through 2.7 allows privilege escalation that can be exploited by abusing systemd service credentials support added to the login(1) implementation of util-linux in release 2.40. This is related to client control over the CREDENTIALSDIRECTORY environment variable, and requires an unprivileged local user to create a login.noauth file."

So is this CVE only for the attack vector reported by Ron Ben Yizhak, and not also for the other attack vector and more general issue reported by Justin Swartz?

If so, are you going to assign a second CVE for the more general issue? I'm not part of the Debian Security Team (I just maintain the inetutils package in Debian), but I think they assigned a CVE because there didn't seem to be one coming from upstream. I guess the expectation would be that if there's a new CVE to be assigned that would be handled by upstream, but if it's needed and it's not forthcoming they might assign another one? (Although the easier way forward would be to reuse the existing one, and issue an update for the DSA.) I am not convinced "the standard CVE process" is exactly as Ron Ben Yizhak describes it above, but I don't mind doing things in this way.

It sometimes happens that a fix is released as being for a certain CVE, and then a second CVE has to be assigned for the "incomplete fix", where the incompleteness of the first fix is the new vulnerability. But with no CVE assigned yet, we didn't have to do it this way. We could have one CVE for the set of issues, and not treat "the original merge" as fixing any CVE at all. To me both issues are caused by the same root cause, so in my mind a single CVE makes sense. But… But again, I don't mind, and I understand that we also need to enable researchers to find and report such issues during work hours, which means making employers happy with credits and CVEs. It isn't wrong to have separate CVEs, so we may. …I don't really care, and I also don't mind if that's very important to the researchers and people are happy to assign new CVEs, I'm happy to add/amend references to any such CVE in the debian/changelog for the package.

My plan right now is to just take the patches that have floated in the bug-inetutils mailing list, fixing the root cause and upload that to Debian as a fix to the existing CVE, as there's otherwise not been much movement there (to merge or include as part of a release). If there's a future CVE assigned I'd amend any references.

Thanks, Guillem

Hi,

On Fri, Mar 06, 2026 at 04:39:23PM +0100, Guillem Jover wrote: Hi!

On Fri, 2026-03-06 at 16:16:49 +0100, Solar Designer wrote: On Fri, Feb 27, 2026 at 01:09:57PM +0100, Guillem Jover wrote: On Tue, 2026-02-24 at 11:57:34 +0200, Ron Ben Yizhak wrote: I’d like to ensure we follow the standard CVE process here. Standard practice dictates that a CVE is issued per individual fix. Generally, once a fix is merged and released, it is assigned its own CVE. Even if that fix is later bypassed, the original merge stands as a unique event in the codebase, meaning we should issue two separate CVEs rather than grouping them. Salvatore Bonaccorso from the Debian Security Team got a CVE assigned for this, see <https://www.cve.org/CVERecord?id=CVE-2026-28372>. I'll update the Debian packaging on the next upload to point to that. The CVE description says:

"telnetd in GNU inetutils through 2.7 allows privilege escalation that can be exploited by abusing systemd service credentials support added to the login(1) implementation of util-linux in release 2.40. This is related to client control over the CREDENTIALSDIRECTORY environment variable, and requires an unprivileged local user to create a login.noauth file."

So is this CVE only for the attack vector reported by Ron Ben Yizhak, and not also for the other attack vector and more general issue reported by Justin Swartz?

If so, are you going to assign a second CVE for the more general issue? I'm not part of the Debian Security Team (I just maintain the inetutils package in Debian), but I think they assigned a CVE because there didn't seem to be one coming from upstream. I guess the expectation would be that if there's a new CVE to be assigned that would be handled by upstream, but if it's needed and it's not forthcoming they might assign another one? (Although the easier way forward would be to reuse the existing one, and issue an update for the DSA.) I just need to clarify one thing here: The CVE was not assigned by the Debian CNA, but as there was no CVE assigned by the issue reported by Ron, I requested one from MITRE. There was none assigned in time when we released the DSA, and at that point TTBOMK the more general issue/root cause indication by Justin Swartz was not known. So the CVE request to MITRE was done specifically as for the issue found by Ron.

Later, after the DSA release on our end, the CVE got assigned.

This is why https://lists.debian.org/debian-security-announce/2026/msg00053.html does not mention any CVE.

So in short: The CVE request to mitre was back then done specific to the issue as it is described in the CVE description, specific to CREDENTIALSDIRECTORY environment variable passing to login(1).

So I think just another CVE should be requested for the more general issue.

Regards, Salvatore

Greetings all,

On Tue, 24 Feb 2026 07:43:51 +0100, Solar Designer wrote: So it looks like in the Linux world non-use of an allow list is specific to InetUtils, which means primarily Debian and derived distros. Based on my interpretation of the whitelisting and path sanitization concepts discussed in this thread, and of the feedback I have received since starting this thread, I have submitted a patch set (including a cover letter [1]) to bug-inetutils which implements:

WHITELISTING

The obsolete blacklist, implemented by scrubenv(), has been removed. The daemon now clears the inherited environment and enforces a default whitelist (USER, LOGNAME, TERM, LANG, and LC) for all NEWENVIRON values.

PATH SANITIZATION

Expanding on the OWL patch approach, every allowed variable is sanitized. Whitelisted variables are dropped if their values contain a path separator ('/'), an explicit reference to the current working directory (".") or an explicit reference to the parent directory ("..").

TELOPTTTYPE INTERCEPTION

The whitelist validation has been extended, in the second version of the patch set, to intercept raw terminal type negotiations (aka TELOPTTTYPE), to prevent questionable TERM payloads from bypassing the NEWENVIRON filter.

OPT-IN EXTENSIBILITY

An --accept-env feature which allows an administrator to extend the white- list with up to 16 additional variables has been included as a separate patch. This feature is not mandatory if there is no need to augment the default whitelist, and may be enabled at build-time via the autoconf flag: --enable-accept-env.

Regards, Justin Swartz

PS: I've attached the patch set inline below this message.

---

[1] https://lists.gnu.org/archive/html/bug-inetutils/2026-03/msg00010.html From 2c6048893f05ef7a34c895b3ea6816710372428b Mon Sep 17 00:00:00 2001 From: Justin Swartz <justin.swartz () risingedge co za> Date: Fri, 6 Mar 2026 19:23:22 +0200 Subject: [PATCH v2 1/2] telnetd: replace environment blacklist with a whitelist.

The previous method of scrubbing environment variables, scrubenv(), and targeted calls to unsetenv() were insufficient to protect against glibc-based injection attacks, such as the recently reported CVE-1999-0073 regression.

To fix this issue, the approach suggested by Simon Josefsson in <https://lists.gnu.org/archive/html/bug-inetutils/2026-02/msg00002.html> has been taken to replace the reactive blacklist with a fairly strict default whitelist of the following allowed environment variables:

USER LOGNAME TERM LANG LC

The daemon now clears the inherited environment (preserving PATH and TERM, respectively, if present) before calling telnetdsetup().

As suggested by Solar Designer, all whitelisted variables are sanitized. This ensures that the variable will be dropped if its value contains a path separator, or a reference to the current working directory or its parent.

telnetd/telnetd.c (main): Call exorciseenv() after argument parsing. telnetd/utility.c (isenvvarallowed): New function. (exorciseenv): New function. Snapshot PATH and TERM, then clearenv(). (getterminaltype): Apply final whitelist validation to terminaltype. (terminaltypeok): Validate terminal type against the whitelist. telnetd/state.c (suboption): Filter NEWENVIRON during parsing using isenvvarallowed() and discard empty values. telnetd/pty.c (startlogin): Remove the obsolete scrubbing logic. telnetd/telnetd.h: Add prototypes for new functions.

--- Changes in v2: - Added value inspection to the isenvvarallowed() validation logic. - Implemented sanitization for whitelisted variables to reject path separators, or reference to the current directory or its parent. - Modified getterminaltype() and terminaltypeok() to prevent TELOPTTTYPE abuse. --- telnetd/pty.c | 32 ---------------- telnetd/state.c | 32 ++++++++++++---- telnetd/telnetd.c | 2 + telnetd/telnetd.h | 3 ++ telnetd/utility.c | 96 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 125 insertions(+), 40 deletions(-)

diff --git a/telnetd/pty.c b/telnetd/pty.c index f3518049..4bf407ad 100644 --- a/telnetd/pty.c +++ b/telnetd/pty.c @@ -83,29 +83,6 @@ startslave (char host, int autologin, char autoname) return master; } -/ - scrubenv() - - Remove a few things from the environment that - don't need to be there. - - Security fix included in telnet-95.10.23.NE of David Borman <deb () cray com>. - / -static void -scrubenv (void) -{ - char cpp, cpp2; - - for (cpp2 = cpp = environ; cpp; cpp++) - { - if (strncmp (cpp, "LD", 3) - && strncmp (cpp, "RLD", 5) - && strncmp (cpp, "LIBPATH=", 8) && strncmp (cpp, "IFS=", 4)) - cpp2++ = cpp; - } - cpp2 = 0; -} - void startlogin (char host, int autologin, char name) { @@ -117,8 +94,6 @@ startlogin (char host, int autologin, char name) (void) autologin; (void) name; - scrubenv (); - / Set the environment variable "LINEMODE" to indicate our linemode / if (lmodetype == REALLINEMODE) setenv ("LINEMODE", "real", 1); @@ -130,13 +105,6 @@ startlogin (char host, int autologin, char name) fatal (net, "can't expand login command line"); argcvget (cmd, "", &argc, &argv); - / util-linux's "login" introduced an authentication bypass method - via environment variable "CREDENTIALSDIRECTORY" in version 2.40. - Clear it from the environment before executing "login" to prevent - abuse via Telnet. - / - unsetenv ("CREDENTIALSDIRECTORY"); - execv (argv[0], argv); syslog (LOGERR, "%s: %m\n", cmd); fatalperror (net, cmd); diff --git a/telnetd/state.c b/telnetd/state.c index a9a51e00..2efc65d4 100644 --- a/telnetd/state.c +++ b/telnetd/state.c @@ -1495,10 +1495,18 @@ suboption (void) case NEWENVVAR: case ENVUSERVAR: cp = '\0'; - if (valp) - setenv (varp, valp, 1); - else - unsetenv (varp); + if (isenvvarallowed (varp, valp)) + { + if (valp) + { + if (valp && valp != 0) + setenv (varp, valp, 1); + } + else + { + unsetenv (varp); + } + } cp = varp = (char ) subpointer; valp = 0; break; @@ -1514,10 +1522,18 @@ suboption (void) } } cp = '\0'; - if (valp) - setenv (varp, valp, 1); - else - unsetenv (varp); + if (isenvvarallowed (varp, valp)) + { + if (valp) + { + if (valp && valp != 0) + setenv (varp, valp, 1); + } + else + { + unsetenv (varp); + } + } break; } / end of case TELOPTNEWENVIRON / #if defined AUTHENTICATION diff --git a/telnetd/telnetd.c b/telnetd/telnetd.c index 219a19da..affa2c96 100644 --- a/telnetd/telnetd.c +++ b/telnetd/telnetd.c @@ -218,6 +218,8 @@ main (int argc, char argv) if (argc != index) error (EXITFAILURE, 0, "junk arguments in the command line"); + exorciseenv (); + telnetdsetup (0); return telnetdrun (); / Never returning. / } diff --git a/telnetd/telnetd.h b/telnetd/telnetd.h index df31a819..5ee59e6e 100644 --- a/telnetd/telnetd.h +++ b/telnetd/telnetd.h @@ -316,6 +316,9 @@ extern void ttysetsofttab (int); extern void ttytspeed (int); extern char expandline (const char fmt); +extern void exorciseenv (void); +extern int isenvvarallowed (const char var, const char val); + / FIXME / extern void termstat (void); diff --git a/telnetd/utility.c b/telnetd/utility.c index 2fe6730c..cabdcdfd 100644 --- a/telnetd/utility.c +++ b/telnetd/utility.c @@ -17,6 +17,16 @@ along with this program. If not, see http://www.gnu.org/licenses/&apos;. / #include <config.h> +#include <fnmatch.h> +#include <string.h> + +#ifdef HAVEPATHSH +# include <paths.h> +#else +# ifndef PATHDEFPATH +# define PATHDEFPATH "/usr/bin:/bin" +# endif +#endif #define TELOPTS #define TELCMDS @@ -65,6 +75,79 @@ static int pcc; extern int not42; +/ A default whitelist for environment variables. / +static const char allowedenvvars[] = { + "USER", + "LOGNAME", + "TERM", + "LANG", + "LC", + NULL +}; + +int +isenvvarallowed (const char var, const char val) +{ + const char p; + int allowed = 0; + + for (p = allowedenvvars; p; p++) + { + if (fnmatch (p, var, FNMNOESCAPE) == 0) + { + allowed = 1; + break; + } + } + + if (!allowed) + return 0; + + if (val != NULL) + { + if (strchr (val, '/') != NULL) + return 0; + + if (strcmp (val, "..") == 0) + return 0; + + if (strcmp (val, ".") == 0) + return 0; + } + + return 1; +} + +void +exorciseenv (void) +{ + char path, term, value; + + value = getenv ("PATH"); + path = value ? strdup (value) : NULL; + + value = getenv ("TERM"); + term = isenvvarallowed ("TERM", value) ? strdup (value) : NULL; + + clearenv(); + + if (path) + { + setenv ("PATH", path, 1); + free (path); + } + else + { + setenv ("PATH", PATHDEFPATH, 1); + } + + if (term) + { + setenv ("TERM", term, 1); + free (term); + } +} + static int readstream (int p, char ibuf, int bufsize) { @@ -863,6 +946,16 @@ getterminaltype (char uname, sizet len) } free (first); free (last); + + / Does TERM appear to be illogical? / + if (terminaltype) + { + if (!isenvvarallowed ("TERM", terminaltype)) + { + free (terminaltype); + terminaltype = NULL; + } + } } return retval; } @@ -876,6 +969,9 @@ getterminaltype (char uname, sizet len) int terminaltypeok (char s) { + if (!isenvvarallowed ("TERM", s)) + return 0; + #ifdef HAVETGETENT char buf[2048]; -- From f541133cd6ed58cbec37442e9eb174f65f59fb28 Mon Sep 17 00:00:00 2001 From: Justin Swartz <justin.swartz () risingedge co za> Date: Sat, 7 Mar 2026 00:41:49 +0200 Subject: [PATCH v2 2/2] telnetd: add optional --accept-env feature to extend the whitelist.

A new command line option, -W or --accept-env, has been added to allow administrators to extend the default whitelist with additional environment variable patterns.

This feature is requires opt-in at compile time and must be enabled using the configure flag: --enable-accept-env

This is useful for systems that require specific variables to be passed to the login process, which would otherwise be dropped by the default whitelist. Any variables allowed via this option are still subject to the global path-traversal sanitization implemented in the previous patch.

configure.ac: Add --enable-accept-env argument and define ENABLEACCEPTENV macro. telnetd/telnetd.h: Add prototype for addallowedenvpattern() guarded by ENABLEACCEPTENV. telnetd/telnetd.c (argpoptions): Add the "W"/"accept-env" option guarded by ENABLEACCEPTENV. (parseopt): Handle the 'W' case by calling addallowedenvpattern(), guarded by ENABLEACCEPTENV. telnetd/utility.c: Declare userenvvars storage and extend isenvvarallowed() to check user patterns first, guarding all additions with ENABLEACCEPTENV. --- configure.ac | 8 ++++++++ telnetd/telnetd.c | 10 ++++++++++ telnetd/telnetd.h | 4 ++++ telnetd/utility.c | 42 ++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 62 insertions(+), 2 deletions(-)

diff --git a/configure.ac b/configure.ac index ac6e6c59..89f73929 100644 --- a/configure.ac +++ b/configure.ac @@ -74,6 +74,14 @@ ACARGENABLE([authentication], [test -z "$KERBEROSVERSION" && ACMSGERROR([--enable-authentication is given, but a Kerberos version is not provided])], [test -z "$KERBEROSVERSION" && enableauthentication=no]) +ACARGENABLE([accept-env], + ASHELPSTRING([--enable-accept-env], + [Enable the --accept-env option in telnetd]), + [if test "$enableval" = yes; then + ACDEFINE([ENABLEACCEPTENV], 1, [Define as 1 to enable the --accept-env option in telnetd]) + fi], + [enableacceptenv=no]) + if test -n "$KERBEROSVERSION"; then test -n "$enableauthentication" || enableauthentication=yes test -n "$enableencryption" || enableencryption=yes diff --git a/telnetd/telnetd.c b/telnetd/telnetd.c index affa2c96..acb6ce08 100644 --- a/telnetd/telnetd.c +++ b/telnetd/telnetd.c @@ -117,6 +117,10 @@ static struct argpoption argpoptions[] = { "do not print host information before login has been completed", GRID}, {"linemode", 'l', "MODE", OPTIONARGOPTIONAL, "set line mode", GRID}, +#ifdef ENABLEACCEPTENV + {"accept-env", 'W', "PATTERN", 0, + "specify an additional environment variable pattern to accept", GRID}, +#endif {"no-keepalive", 'n', NULL, 0, "disable TCP keep-alive", GRID}, {"reverse-lookup", 'U', NULL, 0, @@ -180,6 +184,12 @@ parseopt (int key, char arg, struct argpstate state MAYBEUNUSED) reverselookup = 1; break; +#ifdef ENABLEACCEPTENV + case 'W': + addallowedenvpattern (arg); + break; +#endif + #ifdef AUTHENTICATION case 'X': authdisablename (arg); diff --git a/telnetd/telnetd.h b/telnetd/telnetd.h index 5ee59e6e..259b3126 100644 --- a/telnetd/telnetd.h +++ b/telnetd/telnetd.h @@ -319,6 +319,10 @@ extern char expandline (const char fmt); extern void exorciseenv (void); extern int isenvvarallowed (const char var, const char val); +#ifdef ENABLEACCEPTENV +extern void addallowedenvpattern (const char pattern); +#endif + / FIXME / extern void termstat (void); diff --git a/telnetd/utility.c b/telnetd/utility.c index cabdcdfd..c9d38867 100644 --- a/telnetd/utility.c +++ b/telnetd/utility.c @@ -75,6 +75,12 @@ static int pcc; extern int not42; +#ifdef ENABLEACCEPTENV +# define MAXUSERENVVARS 16 +static const char userenvvars[MAXUSERENVVARS]; +static sizet userenvvarcount = 0; +#endif + / A default whitelist for environment variables. / static const char allowedenvvars[] = { "USER", @@ -90,15 +96,30 @@ isenvvarallowed (const char var, const char val) { const char p; int allowed = 0; +#ifdef ENABLEACCEPTENV + sizet i; - for (p = allowedenvvars; p; p++) + for (i = 0; i < userenvvarcount; i++) { - if (fnmatch (p, var, FNMNOESCAPE) == 0) + if (fnmatch (userenvvars[i], var, FNMNOESCAPE) == 0) { allowed = 1; break; } } +#endif + + if (!allowed) + { + for (p = allowedenvvars; p; p++) + { + if (fnmatch (p, var, FNMNOESCAPE) == 0) + { + allowed = 1; + break; + } + } + } if (!allowed) return 0; @@ -118,6 +139,23 @@ isenvvarallowed (const char var, const char val) return 1; } +#ifdef ENABLEACCEPTENV +void +addallowedenvpattern (const char pattern) +{ + if (!pattern || pattern == 0) + return; + + if (userenvvarcount >= MAXUSERENVVARS) + { + syslog (LOGNOTICE, "Ignoring --accept-env option: limit reached"); + return; + } + + userenvvars[userenvvarcount++] = pattern; +} +#endif + void exorciseenv (void) { --

On Sun, 08 Mar 2026 06:05:45 +0200, Justin Swartz wrote: I'll submit a third version of this patch set later. Based on the feedback provided, the third version of the patch set [1]:

- Leaves the inherited environment intact. - Implements a default whitelist and whitelisted variable value sanitization. - Places the strings of the allowed environment variables array into the .rodata section. - Eliminates duplicated setenv/unsetenv logic in "telnetd/state.c". - Discards the --accept-env feature [3], as an inetutils maintainer [2] is working on an implementation to extend the allowed environment using Gnulib instead.

Find the patch included below.

Regards, Justin

---

[1] https://lists.gnu.org/archive/html/bug-inetutils/2026-03/msg00020.html [2] https://lists.gnu.org/archive/html/bug-inetutils/2026-03/msg00017.html [3] https://lists.gnu.org/archive/html/bug-inetutils/2026-03/msg00018.html From 1b9dc91cfd3c730317aa3bb6ec58ff1beb5dcc15 Mon Sep 17 00:00:00 2001 From: Justin Swartz <justin.swartz () risingedge co za> Date: Sun, 8 Mar 2026 06:55:23 +0200 Subject: [PATCH v3 1/1] telnetd: replace environment blacklist with a whitelist.

The previous method of scrubbing environment variables, scrubenv(), and targeted calls to unsetenv() were insufficient to protect against glibc-based injection attacks, such as the recently reported CVE-1999-0073 regression.

To fix this issue, the approach suggested by Simon Josefsson in <https://lists.gnu.org/archive/html/bug-inetutils/2026-02/msg00002.html> has been taken to replace the reactive blacklist with a fairly strict default whitelist of the following allowed environment variables:

USER LOGNAME TERM LANG LC

And as suggested by Solar Designer, all whitelisted variables will be subject to sanitization, and the inherited environment will be left intact.

Any negotiated variable will be dropped if its value contains a path separator ('/'), or an explicit reference to the current working directory (".") or its parent ("..").

telnetd/utility.c (allowedenvvars): New whitelist array. (isenvvarallowed): New function. (setenvvarifallowed): New helper function. (getterminaltype): Apply final whitelist validation to terminaltype. (terminaltypeok): Validate terminal type against the whitelist. telnetd/state.c (suboption): Filter NEWENVIRON during parsing using setenvvarifallowed(). telnetd/pty.c (startlogin): Remove the obsolete scrubbing logic. telnetd/telnetd.h: Add prototypes for new functions. --- telnetd/pty.c | 32 ------------------ telnetd/state.c | 10 ++---- telnetd/telnetd.h | 3 ++ telnetd/utility.c | 83 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 88 insertions(+), 40 deletions(-)

diff --git a/telnetd/pty.c b/telnetd/pty.c index f3518049..4bf407ad 100644 --- a/telnetd/pty.c +++ b/telnetd/pty.c @@ -83,29 +83,6 @@ startslave (char host, int autologin, char autoname) return master; } -/ - scrubenv() - - Remove a few things from the environment that - don't need to be there. - - Security fix included in telnet-95.10.23.NE of David Borman <deb () cray com>. - / -static void -scrubenv (void) -{ - char cpp, cpp2; - - for (cpp2 = cpp = environ; cpp; cpp++) - { - if (strncmp (cpp, "LD", 3) - && strncmp (cpp, "RLD", 5) - && strncmp (cpp, "LIBPATH=", 8) && strncmp (cpp, "IFS=", 4)) - cpp2++ = cpp; - } - cpp2 = 0; -} - void startlogin (char host, int autologin, char name) { @@ -117,8 +94,6 @@ startlogin (char host, int autologin, char name) (void) autologin; (void) name; - scrubenv (); - / Set the environment variable "LINEMODE" to indicate our linemode / if (lmodetype == REALLINEMODE) setenv ("LINEMODE", "real", 1); @@ -130,13 +105,6 @@ startlogin (char host, int autologin, char name) fatal (net, "can't expand login command line"); argcvget (cmd, "", &argc, &argv); - / util-linux's "login" introduced an authentication bypass method - via environment variable "CREDENTIALSDIRECTORY" in version 2.40. - Clear it from the environment before executing "login" to prevent - abuse via Telnet. - / - unsetenv ("CREDENTIALSDIRECTORY"); - execv (argv[0], argv); syslog (LOGERR, "%s: %m\n", cmd); fatalperror (net, cmd); diff --git a/telnetd/state.c b/telnetd/state.c index a9a51e00..ab6bfb11 100644 --- a/telnetd/state.c +++ b/telnetd/state.c @@ -1495,10 +1495,7 @@ suboption (void) case NEWENVVAR: case ENVUSERVAR: cp = '\0'; - if (valp) - setenv (varp, valp, 1); - else - unsetenv (varp); + setenvvarifallowed (varp, valp); cp = varp = (char ) subpointer; valp = 0; break; @@ -1514,10 +1511,7 @@ suboption (void) } } cp = '\0'; - if (valp) - setenv (varp, valp, 1); - else - unsetenv (varp); + setenvvarifallowed (varp, valp); break; } / end of case TELOPTNEWENVIRON / #if defined AUTHENTICATION diff --git a/telnetd/telnetd.h b/telnetd/telnetd.h index df31a819..8b14d9dd 100644 --- a/telnetd/telnetd.h +++ b/telnetd/telnetd.h @@ -316,6 +316,9 @@ extern void ttysetsofttab (int); extern void ttytspeed (int); extern char expandline (const char fmt); +extern int isenvvarallowed (const char var, const char val); +extern void setenvvarifallowed (const char var, const char val); + / FIXME / extern void termstat (void); diff --git a/telnetd/utility.c b/telnetd/utility.c index 2fe6730c..085065ea 100644 --- a/telnetd/utility.c +++ b/telnetd/utility.c @@ -17,6 +17,16 @@ along with this program. If not, see http://www.gnu.org/licenses/&apos;. / #include <config.h> +#include <fnmatch.h> +#include <string.h> + +#ifdef HAVEPATHSH +# include <paths.h> +#else +# ifndef PATHDEFPATH +# define PATHDEFPATH "/usr/bin:/bin" +# endif +#endif #define TELOPTS #define TELCMDS @@ -65,6 +75,66 @@ static int pcc; extern int not42; +/ A default whitelist for environment variables. / +static const char const allowedenvvars[] = { + "USER", + "LOGNAME", + "TERM", + "LANG", + "LC", + NULL +}; + +int +isenvvarallowed (const char var, const char val) +{ + const char const p; + int allowed = 0; + + for (p = allowedenvvars; p; p++) + { + if (fnmatch (p, var, FNMNOESCAPE) == 0) + { + allowed = 1; + break; + } + } + + if (!allowed) + return 0; + + if (val != NULL) + { + if (strchr (val, '/') != NULL) + return 0; + + if (strcmp (val, "..") == 0) + return 0; + + if (strcmp (val, ".") == 0) + return 0; + } + + return 1; +} + +void +setenvvarifallowed (const char var, const char val) +{ + if (isenvvarallowed (var, val)) + { + if (val) + { + if (val != 0) + setenv (var, val, 1); + } + else + { + unsetenv (var); + } + } +} + static int readstream (int p, char ibuf, int bufsize) { @@ -863,6 +933,16 @@ getterminaltype (char uname, sizet len) } free (first); free (last); + + / Does TERM appear to be illogical? / + if (terminaltype) + { + if (!isenvvarallowed ("TERM", terminaltype)) + { + free (terminaltype); + terminaltype = NULL; + } + } } return retval; } @@ -876,6 +956,9 @@ getterminaltype (char uname, sizet len) int terminaltypeok (char s) { + if (!isenvvarallowed ("TERM", s)) + return 0; + #ifdef HAVETGETENT char buf[2048]; --

Greetings,

I would like to draw the community's attention to the following vulnerability [1], summarized as "Remote Pre-Auth Buffer Overflow in GNU Inetutils telnetd (LINEMODE SLC)", which was reported to the bug-inetutils mailing list recently.

I am not affiliated with the researchers, inetutils, nor GNU/FSF.

Regards, Justin

---

On Thu, Mar 12, 2026 at 08:24:42PM +0200, Justin Swartz wrote: I would like to draw the community's attention to the following vulnerability [1], summarized as "Remote Pre-Auth Buffer Overflow in GNU Inetutils telnetd (LINEMODE SLC)", which was reported to the bug-inetutils mailing list recently.

I am not affiliated with the researchers, inetutils, nor GNU/FSF.

Regards, Justin

---

[1] https://lists.gnu.org/archive/html/bug-inetutils/2026-03/msg00031.html Thank you, Justin!

In cases like this, we should be bringing the entire report to oss-security, not just a link. So I'll include it below.

Further in the above thread, there's a link to a fix pull request by Collin Funk. I didn't review it in full context, but even within the patch context it fails my review:

addslc (char func, char flag, cct val) {

/ Do nothing if the entire triplet cannot fit in the buffer. / if (slcbuf + sizeof slcbuf <= slcptr + 6) return;

if ((slcptr++ = (unsigned char) func) == 0xff) slcptr++ = 0xff;

if ((slcptr++ = (unsigned char) flag) == 0xff) slcptr++ = 0xff;

if ((slcptr++ = (unsigned char) val) == 0xff) slcptr++ = 0xff;

} / end of addslc /

In "slcptr + 6", it appears to rely on pointer math working outside of the object, but that's UB in C. If the C compiler concludes that the "if" condition cannot be true within defined behavior, it is free to optimize the entire "if" and "return" out. A proper check may be:

if (slcbuf + sizeof slcbuf - 6 <= slcptr)

or perhaps with "<" in place of "<=", unless we need an extra element for some reason.

For comparison, here's the function from netkit-telnet-0.17/telnetd/slc.c:

static unsigned char slcbuf[NSLC6]; / buffer for slc negotiation /

static void addslcbufrawchar(unsigned char ch) { if (slcoff < sizeof(slcbuf)) { slcbuf[slcoff++] = ch; } }

static void addslcbufchar(unsigned char ch) { addslcbufrawchar(ch); if (ch==0xff) { addslcbufrawchar(0xff); } }

#ifdef LINEMODE / addslc Add an slc triplet to the slc buffer. / void addslc(char func, char flag, cct val) { addslcbufchar(func); addslcbufchar(flag); addslcbufchar(val); }

Alexander

--- From: Adiel Sol Subject: Remote Pre-Auth Buffer Overflow in GNU Inetutils telnetd (LINEMODE SLC) Date: Wed, 11 Mar 2026 12:20:57 +0000

Hi GNU Inetutils / Savannah Security Team,

I would like to report a security vulnerability in GNU Inetutils telnetd.

Summary

The telnetd server has a buffer overflow in the LINEMODE SLC (Set Local Characters) suboption handler. An unauthenticated attacker can trigger it by connecting to port 23 and sending a crafted SLC suboption with many triplets. No login is required; the bug is hit during option negotiation, before the login prompt. The overflow corrupts memory and can be turned into arbitrary writes. In practice this can lead to remote code execution. Because telnetd usually runs as root (e.g. under inetd or xinetd), a successful exploit would give the attacker full control of the system.

Affected Component

telnetd/slc.c (functions addslc, processslc, dooptslc; buffer slcbuf at line 59) Versions: All GNU Inetutils through 2.7, including current development HEAD. Any telnetd derived from the BSD codebase that still uses this SLC implementation may also be affected.

Root Cause

The SLC response is built in a fixed 108-byte buffer, slcbuf, with only 104 bytes used for data after a 4-byte header. The function addslc() (lines 162-175) appends 3 bytes per SLC triplet but never checks whether the buffer is full. The pointer slcptr is just incremented each time. When the server gets an SLC suboption, it calls processslc() for each triplet. For any triplet with function code greater than 18 (NSLC), the server calls addslc() to queue a "not supported" reply. There is no limit on how many such replies are queued. The client can send a long SLC suboption (the suboption buffer is 512 bytes, so about 170 triplets). After about 35 triplets with func > 18, the 104-byte space is exceeded and the code writes past the end of slcbuf. That corrupts whatever lies after it in BSS (including the slcptr pointer). Later, endslc() uses the corrupted slcptr to write the suboption end marker, which gives the attacker an arbitrary write in memory. So the bug is a classic buffer overflow with no bounds check (CWE-120, CWE-787).

Impact

CVSS 3.1: AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H (9.8 Critical) CWE-120 (Buffer Copy without Checking Size of Input), CWE-787 (Out-of-bounds Write) Any unauthenticated attacker with network access to port 23 can trigger the overflow with a single telnet connection and a crafted SLC suboption. Successful exploitation can lead to arbitrary code execution as root and full compromise of the host (backdoors, data exfiltration, pivot, etc.).

Proof of Concept

1. Start GNU Inetutils telnetd (e.g. with inetd or run telnetd manually) so it listens on port 23. 2. From another machine, connect to the telnet port and complete the initial handshake. When the server sends DO LINEMODE, reply with WILL LINEMODE so the server enters LINEMODE negotiation. 3. Send a single LINEMODE SLC suboption containing at least 40 to 50 triplets, each with a function code greater than 18 (e.g. 19, 20, 21, ... 68). Each triplet is 3 bytes (func, flag, value). Use 0x00 for flag and value. The suboption must be properly framed with IAC SB LINEMODE LMSLC at the start and IAC SE at the end. 4. The server will call addslc() for each triplet. After about 35 triplets it will write past the end of slcbuf. You should observe a crash, or (if you craft the overflow) memory corruption and possibly code execution.

Credit Request

We kindly request that the following researchers be credited for this discovery: Adiel Sol, Arad Inbar, Erez Cohen, Nir Somech, Ben Grinberg, Daniel Lubel - DREAM Security Research Team Best regards,     DREAM Security Research Team

Attachment: 13-16-27.mp4

Solar Designer <solar () openwall com> writes: Thank you, Justin!

In cases like this, we should be bringing the entire report to oss-security, not just a link. So I'll include it below.

Further in the above thread, there's a link to a fix pull request by Collin Funk. I didn't review it in full context, but even within the patch context it fails my review:

addslc (char func, char flag, cct val) {

/ Do nothing if the entire triplet cannot fit in the buffer. / if (slcbuf + sizeof slcbuf <= slcptr + 6) return;

if ((slcptr++ = (unsigned char) func) == 0xff) slcptr++ = 0xff;

if ((slcptr++ = (unsigned char) flag) == 0xff) slcptr++ = 0xff;

if ((slcptr++ = (unsigned char) val) == 0xff) slcptr++ = 0xff;

} / end of addslc /

In "slcptr + 6", it appears to rely on pointer math working outside of the object, but that's UB in C. If the C compiler concludes that the "if" condition cannot be true within defined behavior, it is free to optimize the entire "if" and "return" out. CC'ing bug-gnulib. Do we make any assumptions about this behavior in Gnulib? I know we generally assume systems are more sane than ISO C requires. E.g. no holes in integers, flat address space, etc. Perhaps it is worth another bullet point in our documentation [1]. A proper check may be:

if (slcbuf + sizeof slcbuf - 6 <= slcptr)

or perhaps with "<" in place of "<=", unless we need an extra element for some reason. Yeah, that works. Thanks.

Collin

[1] https://www.gnu.org/software/gnulib/manual/gnulib.html#Other-portability-assumptions-made-by-Gnulib

On 2026-03-12 13:57, Collin Funk wrote: Solar Designer <solar () openwall com> writes:... / Do nothing if the entire triplet cannot fit in the buffer. / if (slcbuf + sizeof slcbuf <= slcptr + 6) return; In "slcptr + 6", it appears to rely on pointer math working outside of the object, but that's UB in C. That's right. CC'ing bug-gnulib. Do we make any assumptions about this behavior in Gnulib?

A proper check may be:

if (slcbuf + sizeof slcbuf - 6 <= slcptr)

if (slcbuf + sizeof slcbuf - slcptr <= 6)

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203