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)
 {
-- 


Reply via email to