Re: Telnetd Vulnerability Report

Justin Swartz <[email protected]> Sun, 08 Mar 2026 06:05:45 +0200
Newsgroups gmane.comp.gnu.inetutils.bugs,gmane.comp.security.oss.general
Message-ID <[email protected]>
On Sun, 8 Mar 2026 03:57:45 +0100, Solar Designer wrote:
> I'm not saying you should revise the list in any way - just sharing 
> what
> others have.  It may well be that allowing those other env vars by
> default is obsolete since use cases for telnet are now more 
> specialized,
> and maybe allowing LANG and LC_* is desirable for current use cases.

LANG and LC_* were cargo-culted in as they are honoured by OpenSSHd.


> Separately note that I didn't check the *BSDs telnet _client_ (which I
> think is still present in all *BSDs) for being (hopefully not) willing
> to export arbitrary env vars.  The maintainers could want to check 
> this.
> And you could want to check the telnet client in InetUtils, now that we
> know this package missed telnet[d] security fixes in general.  This was
> CVE-2005-0488 (and CVE-2005-1205 on Windows).
> 
> "Certain BSD-based Telnet clients, including those used on Solaris and
> SuSE Linux, allow remote malicious Telnet servers to read sensitive
> environment variables via the NEW-ENVIRON option with a SEND 
> ENV_USERVAR
> command."

I don't mind poking at the InetUtils telnet client once I've done my 
best
to leave the telnetd implementation in a better state than I found it 
in.


> > The daemon now clears the inherited environment (preserving PATH
> > and TERM, respectively, if present) before calling telnetd_setup().
> 
> Inherited from inetd or the like?  It's supposed to be trusted input 
> and
> env vars in there may be set on purpose, so dropping them is 
> unexpected.
> I think e.g. sshd doesn't do that, why would telnetd?  Think things 
> like
> LD_PRELOAD=/lib64/libhardened_malloc.so (although /etc/ld.so.preload is
> a more reliable way to do this when practical to do it globally).

Yes, inherited from inetd. Your reasoning makes sense, so I'll get rid 
of
exorcise_env() and leave the inetd/tcpd supplied environment intact for
telnetd (or some site-specific wrapper) to inherit.


> > +++ b/telnetd/state.c
> > @@ -1495,10 +1495,18 @@ suboption (void)
> >  	      case NEW_ENV_VAR:
> >  	      case ENV_USERVAR:
...
> >        }				/* end of case TELOPT_NEW_ENVIRON */
> 
> Some code duplication here.  Not new with these changes, but could be
> worth moving to a new function e.g. set_env_var_if_allowed().

Agreed. I'll implement set_env_var_if_allowed() instead.


> > +/* A default whitelist for environment variables. */
> > +static const char *allowed_env_vars[] = {
> > +  "USER",
> > +  "LOGNAME",
> > +  "TERM",
> > +  "LANG",
> > +  "LC_*",
> > +  NULL
> > +};
> 
> Can make not only the strings but also the pointers const:
> 
> static const char * const allowed_env_vars[] = {
> 
> so that both may end up in a read-only section.

OK, .rodata it is.


> > +int
> > +is_env_var_allowed (const char *var, const char *val)
> > +{
> > +  const char **p;
> > +  int allowed = 0;
> > +
> > +  for (p = allowed_env_vars; *p; p++)
> > +    {
> > +      if (fnmatch (*p, var, FNM_NOESCAPE) == 0)
> > +        {
> > +          allowed = 1;
> > +          break;
> > +        }
> > +    }
> > +
> > +  if (!allowed)
> > +    return 0;
> 
> You didn't strictly need the "allowed" variable, you could check *p
> after the loop.  But maybe it's more readable the way you wrote it.

I'll keep "allowed" for the time being, but I don't mind changing it if
there's a clearer way to express this logic.


> My review above isn't in full context - I only looked at the patches.

Thanks for the review, I'll submit a third version of this patch set 
later.