CVE-2023-42465 (C, published 2023-12-22T16:15Z)
Description: value: Sudo before 1.9.15 might allow row hammer attacks (for authentication bypass or privilege escalation) because application logic sometimes is based on not equaling an error value (instead of equaling a success value), and because the values do not resist flips of a single bit.
Severity: nan (CVSS v3 base score: 7)
Weakness: NVD-CWE-noinfo \u2014 Insufficient Information
There is insufficient information about the issue to classify it; details are unkown or unspecified.
Repository: https://github.com/sudo-project/sudo
Files changed:
plugins/sudoers/auth/passwd.c
plugins/sudoers/auth/sudo_auth.c
plugins/sudoers/auth/sudo_auth.h
plugins/sudoers/lookup.c
plugins/sudoers/match.c
plugins/sudoers/parse.h
Commit message: Try to make sudo less vulnerable to ROWHAMMER attacks.

We now use ROWHAMMER-resistent values for ALLOW, DENY, AUTH_SUCCESS,
AUTH_FAILURE, AUTH_ERROR and AUTH_NONINTERACTIVE.  In addition, we
explicitly test for expected values instead of using a negated test
against an error value.  In the parser match functions this means
explicitly checking for ALLOW or DENY instead of accepting anything
that is not set to UNSPEC.

Thanks to Andrew J. Adiletta, M. Caner Tol, Yarkin Doroz, and Berk
Sunar, all affiliated with the Vernam Applied Cryptography and
Cybersecurity Lab at Worcester Polytechnic Institute, for the report.
Paper preprint: https://arxiv.org/abs/2309.02545


Vulnerable code:
    int matched = 0;
	strlcpy(des_pass, pass, sizeof(des_pass));
	if (HAS_AGEINFO(pw_epasswd, pw_len) && strlen(epass) == DESLEN)
	    matched = !strncmp(pw_epasswd, epass, DESLEN);
	else
	    matched = !strcmp(pw_epasswd, epass);
    debug_return_int(matched ? AUTH_SUCCESS : AUTH_FAILURE);
    int matched;
    matched = !strcmp(pass, pw_passwd);
    debug_return_int(matched ? AUTH_SUCCESS : AUTH_FAILURE);
	    if (status == AUTH_FAILURE)
	    else if (status == AUTH_ERROR)
		break;		/* assume error msg already printed */
    debug_return_int(status == AUTH_ERROR ? -1 : 0);
	    if (status == AUTH_ERROR) {
		else if (status == AUTH_ERROR || user_interrupted())
	case AUTH_ERROR:
 * Returns 1 on success and -1 on error.
	    if (status != AUTH_SUCCESS) {
		debug_return_int(-1);
    debug_return_int(1);
 * Returns 1 on success and -1 on error.
	    if (status == AUTH_ERROR) {
		debug_return_int(-1);
    debug_return_int(1);
/* Auth function return values.  */
#define AUTH_SUCCESS		0
#define AUTH_FAILURE		1
#define AUTH_INTR		2
#define AUTH_ERROR		3
#define AUTH_NONINTERACTIVE	4
		if (callback != NULL && user_match != UNSPEC) {
		    if (cmnd_match != UNSPEC) {
	    if (callback != NULL && user_match != UNSPEC) {
		if (cmnd_match != UNSPEC) {
	if (m != UNSPEC) {
    if (match != UNSPEC) {
		if (rc != UNSPEC) {
	if ((matched = user_matches(parse_tree, pw, m)) != UNSPEC)
		    if (rc != UNSPEC) {
	if (user_matched != UNSPEC) {
			if (rc != UNSPEC) {
	    if (group_matched != UNSPEC) {
    if (group_matched == UNSPEC) {
	if (matched != UNSPEC)
		if (rc != UNSPEC) {
	if (matched != UNSPEC)
		if (rc != UNSPEC) {
		    if (matched != UNSPEC) {
#undef DENY
#define DENY	 0
#undef ALLOW
#define ALLOW	 1
    ((tt) != UNSPEC && (tt) != IMPLIED)

Fixed code:
    int ret;
	(void)strlcpy(des_pass, pass, sizeof(des_pass));
    ret = AUTH_FAILURE;
	if (HAS_AGEINFO(pw_epasswd, pw_len) && strlen(epass) == DESLEN) {
	    if (strncmp(pw_epasswd, epass, DESLEN) == 0)
		ret = AUTH_SUCCESS;
	} else {
	    if (strcmp(pw_epasswd, epass) == 0)
		ret = AUTH_SUCCESS;
	}
    debug_return_int(ret);
    int ret;
    if (strcmp(pass, pw_passwd) == 0)
	ret = AUTH_SUCCESS;
    else
	ret = AUTH_FAILURE;
    debug_return_int(ret);
	    switch (status) {
	    case AUTH_SUCCESS:
		break;
	    case AUTH_FAILURE:
		break;
	    default:
		/* Assume error msg already printed. */
		debug_return_int(-1);
	    }
    debug_return_int(0);
	    if (status != AUTH_SUCCESS) {
		else if (status != AUTH_SUCCESS || user_interrupted())
 * Returns true on success, false on failure and -1 on error.
    int ret = true;
	    switch (status) {
	    case AUTH_SUCCESS:
		break;
	    case AUTH_FAILURE:
		ret = false;
		break;
	    default:
		ret = -1;
		break;
    debug_return_int(ret);
 * Returns true on success, false on failure and -1 on error.
    int ret = true;
	    switch (status) {
	    case AUTH_SUCCESS:
		break;
	    case AUTH_FAILURE:
		ret = false;
		break;
	    default:
		ret = -1;
		break;
    debug_return_int(ret);
/* Auth function return values (rowhammer resistent).  */
#define AUTH_SUCCESS		0x52a2925	/* 0101001010100010100100100101 */
#define AUTH_FAILURE		0xad5d6da	/* 1010110101011101011011011010 */
#define AUTH_INTR		0x69d61fc8	/* 1101001110101100001111111001000 */
#define AUTH_ERROR		0x1629e037	/* 0010110001010011110000000110111 */
#define AUTH_NONINTERACTIVE	0x1fc8d3ac	/* 11111110010001101001110101100 */
		if (callback != NULL && user_match == DENY) {
		    if (SPECIFIED(cmnd_match)) {
	    if (callback != NULL && user_match == DENY) {
		if (SPECIFIED(cmnd_match)) {
	if (SPECIFIED(m)) {
    if (SPECIFIED(match)) {
		if (SPECIFIED(rc)) {
	matched = user_matches(parse_tree, pw, m);
	if (SPECIFIED(matched))
		    if (SPECIFIED(rc)) {
	if (SPECIFIED(user_matched)) {
			if (SPECIFIED(rc)) {
	    if (SPECIFIED(group_matched)) {
    if (!SPECIFIED(group_matched)) {
	if (SPECIFIED(matched))
		if (SPECIFIED(rc)) {
	if (SPECIFIED(matched))
		if (SPECIFIED(rc)) {
		    if (SPECIFIED(matched)) {
/* Allowed by policy (rowhammer resistent). */
#undef ALLOW
#define ALLOW	 0x52a2925	/* 0101001010100010100100100101 */

/* Denied by policy (rowhammer resistent). */
#undef DENY
#define DENY	 0xad5d6da	/* 1010110101011101011011011010 */

/* Neither allowed, nor denied. */

/* Tag implied by root access (SETENV only). */
/*
 * We must explicitly check against ALLOW and DENY instead testing
 * that the value is not UNSPEC to avoid potential ROWHAMMER issues.
 */
#define SPECIFIED(_v)	((_v) == ALLOW || (_v) == DENY)

    ((tt) == true || (tt) == false)