Follow @Openwall on Twitter for new release announcements and other news
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20261106230259.GY3542221@port70.net>
Date: Sat, 7 Nov 2026 00:02:59 +0100
From: Szabolcs Nagy <nsz@...t70.net>
To: Rich Felker <dalias@...c.org>
Cc: musl@...ts.openwall.com
Subject: Re: [PATCH] [RFC] Harden against misuse of crypt

* Rich Felker <dalias@...c.org> [2026-11-06 16:54:05 -0500]:
> This patch changes crypt to return "*0" instead of "*" as the "failure
> token" that will not match any password.
> 
> Likewise, "*1" is used in place of "x" if the failure token would be
> identical to the input salt, but this case is presently unreachable.
> Since the existing DES path interprets characters outside the portable
> range as valid DES salt, passing a setting of "*0" will not produce
> "*1" but instead a DES-hashed password with "*0" as the salt. I don't
> see any problem with this -- "*0" should never appear in the
> passwd/shadow db anyway, and if it somehow did, crypt(pass,"*0") would
> still return something not-equal to "*0": "*0" followed by 11
> characters of DES hash.
> 
> In any case, I put the "*1" code path in there in case anything ever
> changes with how we handle DES, to be sure crypt(pass,"*0") can never
> return "*0".
> 
> Rich

looks good,
but this commit has no reference to the analysis in
https://www.openwall.com/lists/musl/2026/11/06/7
i would add a link. or a note that the misuse was observed in
the wild motivating the change. and that "*0" has historical
precendent so broken callers are less likely.

as a friend put it, if the goal is to minimize the likelihood
of the failure token matching a passwd entry then it is
possible to do better than "*0": e.g. 100 non-printable bytes
(in case we really want to be defensive about misuses and typos
in passwd files)

but if the plan is to move to return NULL then i guess "*0" is
the least risky solution for now.

> diff --git a/src/crypt/crypt_blowfish.c b/src/crypt/crypt_blowfish.c
> index d722607b..0bb82360 100644
> --- a/src/crypt/crypt_blowfish.c
> +++ b/src/crypt/crypt_blowfish.c
> @@ -802,5 +802,5 @@ char *__crypt_blowfish(const char *key, const char *setting, char *output)
>  	if (ok && retval)
>  		return retval;
>  
> -	return "*";
> +	return "*0";
>  }
> diff --git a/src/crypt/crypt_des.c b/src/crypt/crypt_des.c
> index 338a8f37..38e63395 100644
> --- a/src/crypt/crypt_des.c
> +++ b/src/crypt/crypt_des.c
> @@ -1012,5 +1012,5 @@ char *__crypt_des(const char *key, const char *setting, char *output)
>  	if (p && !strcmp(p, test_hash) && retval)
>  		return retval;
>  
> -	return (setting[0]=='*') ? "x" : "*";
> +	return (setting[0]!='*' || setting[1]!='0') ? "*0" : "*1";
>  }
> diff --git a/src/crypt/crypt_md5.c b/src/crypt/crypt_md5.c
> index 6e75b36c..e8c609f1 100644
> --- a/src/crypt/crypt_md5.c
> +++ b/src/crypt/crypt_md5.c
> @@ -4,7 +4,7 @@
>   * original md5 crypt design is from Poul-Henning Kamp
>   * this implementation was created based on the code in freebsd
>   * at least 32bit int is assumed, key is limited and $1$ prefix is mandatory,
> - * on error "*" is returned
> + * on error "*0" is returned
>   */
>  #include <string.h>
>  #include <stdint.h>
> @@ -280,6 +280,6 @@ char *__crypt_md5(const char *key, const char *setting, char *output)
>  	/* self test and stack cleanup */
>  	q = md5crypt(testkey, testsetting, testbuf);
>  	if (!p || q != testbuf || memcmp(testbuf, testhash, sizeof testhash))
> -		return "*";
> +		return "*0";
>  	return p;
>  }
> diff --git a/src/crypt/crypt_sha256.c b/src/crypt/crypt_sha256.c
> index e885dc68..8d9c7e73 100644
> --- a/src/crypt/crypt_sha256.c
> +++ b/src/crypt/crypt_sha256.c
> @@ -5,7 +5,7 @@
>   * in this implementation at least 32bit int is assumed,
>   * key length is limited, the $5$ prefix is mandatory, '\n' and ':' is rejected
>   * in the salt and rounds= setting must contain a valid iteration count,
> - * on error "*" is returned.
> + * on error "*0" is returned.
>   */
>  #include <ctype.h>
>  #include <stdlib.h>
> @@ -317,6 +317,6 @@ char *__crypt_sha256(const char *key, const char *setting, char *output)
>  	/* self test and stack cleanup */
>  	q = sha256crypt(testkey, testsetting, testbuf);
>  	if (!p || q != testbuf || memcmp(testbuf, testhash, sizeof testhash))
> -		return "*";
> +		return "*0";
>  	return p;
>  }
> diff --git a/src/crypt/crypt_sha512.c b/src/crypt/crypt_sha512.c
> index 39970caf..c9973f0e 100644
> --- a/src/crypt/crypt_sha512.c
> +++ b/src/crypt/crypt_sha512.c
> @@ -5,7 +5,7 @@
>   * in this implementation at least 32bit int is assumed,
>   * key length is limited, the $6$ prefix is mandatory, '\n' and ':' is rejected
>   * in the salt and rounds= setting must contain a valid iteration count,
> - * on error "*" is returned.
> + * on error "*0" is returned.
>   */
>  #include <ctype.h>
>  #include <stdlib.h>
> @@ -366,6 +366,6 @@ char *__crypt_sha512(const char *key, const char *setting, char *output)
>  	/* self test and stack cleanup */
>  	q = sha512crypt(testkey, testsetting, testbuf);
>  	if (!p || q != testbuf || memcmp(testbuf, testhash, sizeof testhash))
> -		return "*";
> +		return "*0";
>  	return p;
>  }

Powered by blists - more mailing lists

Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.