$NetBSD: patch-CVE-2012-5640,v 1.1 2026/09/25 14:20:39 wiz Exp $

CVE-2012-5640 -- denial of service.  crypt() returns NULL for a salt it
does not recognise (glibc and illumos do; NetBSD and current Linux
return "*0" instead).  thttpd passes the result straight on in all three
places it calls crypt(), and the salt comes from a user-written
.htpasswd, so one malformed line is enough.

In auth_check2() the result is handed to strcmp(), so a request for the
protected directory crashes the server.  Note that the fix must keep
going to the send_authenticate() call below rather than returning
straight away: this function documents -1 as "unauthorized" and every
existing -1 is preceded by send_authenticate(), and the callers take -1
to mean a response has already been written.  Returning -1 without it
would answer the request with nothing at all.  Fedora and Alpine carry
this fix in the shorter "return -1" form, which has that effect.  A
syslog line is added because an unusable salt is an administrator's
mistake, not a wrong password.

In extras/htpasswd.c the result is passed to fprintf("%s"), so writing a
password with a salt crypt() dislikes dereferences NULL.  Fedora and
Alpine guard this one too.

ACME lists a crypt() NULL check for the unreleased 2.30.

--- libhttpd.c.orig
+++ libhttpd.c
@@ -1030,6 +1030,7 @@
     FILE* fp;
     char line[500];
     char* cryp;
+    char* cryp2;
     static char* prevauthpath;
     static size_t maxprevauthpath = 0;
     static time_t prevmtime;
@@ -1082,8 +1083,16 @@
 	 sb.st_mtime == prevmtime &&
 	 strcmp( authinfo, prevuser ) == 0 )
 	{
-	/* Yes.  Check against the cached encrypted password. */
-	if ( strcmp( crypt( authpass, prevcryp ), prevcryp ) == 0 )
+	/* Yes.  Check against the cached encrypted password.  crypt() returns
+	** NULL for a salt it does not understand, and the password file is
+	** user-supplied, so a bad line must not be dereferenced.  Treat it as
+	** a mismatch so that the send_authenticate() below still runs: the
+	** callers take -1 to mean a response has already been sent.
+	*/
+	cryp = crypt( authpass, prevcryp );
+	if ( cryp == (char*) 0 )
+	    syslog( LOG_ERR, "unusable password for %.80s in %.80s", authinfo, authpath );
+	if ( cryp != (char*) 0 && strcmp( cryp, prevcryp ) == 0 )
 	    {
 	    /* Ok! */
 	    httpd_realloc_str(
@@ -1131,8 +1140,11 @@
 	    {
 	    /* Yes. */
 	    (void) fclose( fp );
-	    /* So is the password right? */
-	    if ( strcmp( crypt( authpass, cryp ), cryp ) == 0 )
+	    /* So is the password right?  As above, crypt() may return NULL. */
+	    cryp2 = crypt( authpass, cryp );
+	    if ( cryp2 == (char*) 0 )
+		syslog( LOG_ERR, "unusable password for %.80s in %.80s", line, authpath );
+	    if ( cryp2 != (char*) 0 && strcmp( cryp2, cryp ) == 0 )
 		{
 		/* Ok! */
 		httpd_realloc_str(
--- extras/htpasswd.c.orig
+++ extras/htpasswd.c
@@ -131,6 +131,11 @@
     (void) srandom( (int) time( (time_t*) 0 ) );
     to64( &salt[0], random(), 2 );
     cpw = crypt( pw, salt );
+    if ( cpw == (char*) 0 )
+	{
+	(void) fprintf( stderr, "htpasswd: crypt() could not hash the password\n" );
+	exit( 1 );
+	}
     (void) fprintf( f, "%s:%s\n", user, cpw );
     }
 
