The previous method of scrubbing environment variables, scrub_env(), 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 also clears the inherited environment (preserving PATH and TERM, respectively, if present) before calling telnetd_setup(). * telnetd/telnetd.c (main): Call exorcise_env() after argument parsing. * telnetd/utility.c (is_env_var_allowed): New function. (exorcise_env): New function. Snapshot PATH and TERM, then clearenv(). * telnetd/state.c (suboption): Filter NEW_ENVIRON during parsing using is_env_var_allowed() and discard empty values. * telnetd/pty.c (start_login): Remove the obsolete scrubbing logic. * telnetd/telnetd.h: Add prototypes for new functions. --- telnetd/pty.c | 32 ------------------------ telnetd/state.c | 26 ++++++++++++++++--- telnetd/telnetd.c | 2 ++ telnetd/telnetd.h | 3 +++ telnetd/utility.c | 64 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 91 insertions(+), 36 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; } -/* - * scrub_env() - * - * 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 <[email protected]>. - */ -static void -scrub_env (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 start_login (char *host, int autologin, char *name) { @@ -117,8 +94,6 @@ start_login (char *host, int autologin, char *name) (void) autologin; (void) name; - scrub_env (); - /* Set the environment variable "LINEMODE" to indicate our linemode */ if (lmodetype == REAL_LINEMODE) setenv ("LINEMODE", "real", 1); @@ -130,13 +105,6 @@ start_login (char *host, int autologin, char *name) fatal (net, "can't expand login command line"); argcv_get (cmd, "", &argc, &argv); - /* util-linux's "login" introduced an authentication bypass method - * via environment variable "CREDENTIALS_DIRECTORY" in version 2.40. - * Clear it from the environment before executing "login" to prevent - * abuse via Telnet. - */ - unsetenv ("CREDENTIALS_DIRECTORY"); - execv (argv[0], argv); syslog (LOG_ERR, "%s: %m\n", cmd); fatalperror (net, cmd); diff --git a/telnetd/state.c b/telnetd/state.c index a9a51e00..68b33c9b 100644 --- a/telnetd/state.c +++ b/telnetd/state.c @@ -1496,9 +1496,18 @@ suboption (void) case ENV_USERVAR: *cp = '\0'; if (valp) - setenv (varp, valp, 1); + { + if (is_env_var_allowed (varp)) + { + if (valp && *valp != 0) + setenv (varp, valp, 1); + } + } else - unsetenv (varp); + { + if (is_env_var_allowed (varp)) + unsetenv (varp); + } cp = varp = (char *) subpointer; valp = 0; break; @@ -1515,9 +1524,18 @@ suboption (void) } *cp = '\0'; if (valp) - setenv (varp, valp, 1); + { + if (is_env_var_allowed (varp)) + { + if (valp && *valp != 0) + setenv (varp, valp, 1); + } + } else - unsetenv (varp); + { + if (is_env_var_allowed (varp)) + unsetenv (varp); + } break; } /* end of case TELOPT_NEW_ENVIRON */ #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 (EXIT_FAILURE, 0, "junk arguments in the command line"); + exorcise_env (); + telnetd_setup (0); return telnetd_run (); /* Never returning. */ } diff --git a/telnetd/telnetd.h b/telnetd/telnetd.h index df31a819..c9914a2e 100644 --- a/telnetd/telnetd.h +++ b/telnetd/telnetd.h @@ -316,6 +316,9 @@ extern void tty_setsofttab (int); extern void tty_tspeed (int); extern char *expand_line (const char *fmt); +extern void exorcise_env (void); +extern int is_env_var_allowed (const char *var); + /* FIXME */ extern void _termstat (void); diff --git a/telnetd/utility.c b/telnetd/utility.c index 2fe6730c..590f545a 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/'. */ #include <config.h> +#include <fnmatch.h> +#include <string.h> + +#ifdef HAVE_PATHS_H +# include <paths.h> +#else +# ifndef _PATH_DEFPATH +# define _PATH_DEFPATH "/usr/bin:/bin" +# endif +#endif #define TELOPTS #define TELCMDS @@ -65,6 +75,60 @@ static int pcc; extern int not42; +/* A default whitelist for environment variables. */ +static const char *allowed_env_vars[] = { + "USER", + "LOGNAME", + "TERM", + "LANG", + "LC_*", + NULL +}; + +int +is_env_var_allowed (const char *var) +{ + const char **p; + + for (p = allowed_env_vars; *p; p++) + { + if (fnmatch (*p, var, FNM_NOESCAPE) == 0) + return 1; + } + + return 0; +} + +void +exorcise_env (void) +{ + char *path, *term, *value; + + value = getenv ("PATH"); + path = value ? strdup (value) : NULL; + + value = getenv ("TERM"); + term = value ? strdup (value) : NULL; + + clearenv(); + + if (path) + { + setenv ("PATH", path, 1); + free (path); + } + else + { + setenv ("PATH", _PATH_DEFPATH, 1); + } + + if (term) + { + setenv ("TERM", term, 1); + free (term); + } +} + static int readstream (int p, char *ibuf, int bufsize) { --
