From 6d52f48a6e5e64ffb6043bd2edaf1671e7c1a97c Mon Sep 17 00:00:00 2001 From: wangkaiqiang Date: Mon, 19 Feb 2024 10:27:04 +0800 Subject: [PATCH] fix CVE-2023-42465 --- sudo-1.9.15-CVE-2023-42465.patch | 598 +++++++++++++++++++++++++++++++ sudo.spec | 8 +- 2 files changed, 605 insertions(+), 1 deletion(-) create mode 100644 sudo-1.9.15-CVE-2023-42465.patch diff --git a/sudo-1.9.15-CVE-2023-42465.patch b/sudo-1.9.15-CVE-2023-42465.patch new file mode 100644 index 0000000..f7f8458 --- /dev/null +++ b/sudo-1.9.15-CVE-2023-42465.patch @@ -0,0 +1,598 @@ +diff -up ./plugins/sudoers/auth/passwd.c.rowhammer ./plugins/sudoers/auth/passwd.c +--- ./plugins/sudoers/auth/passwd.c.rowhammer 2020-12-17 02:33:44.000000000 +0100 ++++ ./plugins/sudoers/auth/passwd.c 2024-01-22 16:01:16.331874669 +0100 +@@ -62,7 +62,7 @@ sudo_passwd_verify(struct passwd *pw, ch + char sav, *epass; + char *pw_epasswd = auth->data; + size_t pw_len; +- int matched = 0; ++ int ret; + debug_decl(sudo_passwd_verify, SUDOERS_DEBUG_AUTH); + + /* An empty plain-text password must match an empty encrypted password. */ +@@ -85,27 +85,37 @@ sudo_passwd_verify(struct passwd *pw, ch + */ + epass = (char *) crypt(pass, pw_epasswd); + pass[8] = sav; ++ ret = AUTH_FAILURE; + if (epass != NULL) { +- if (HAS_AGEINFO(pw_epasswd, pw_len) && strlen(epass) == DESLEN) +- matched = !strncmp(pw_epasswd, epass, DESLEN); +- else +- matched = !strcmp(pw_epasswd, epass); ++ 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(matched ? AUTH_SUCCESS : AUTH_FAILURE); ++ explicit_bzero(des_pass, sizeof(des_pass)); ++ ++ debug_return_int(ret); + } + #else + int + sudo_passwd_verify(struct passwd *pw, char *pass, sudo_auth *auth, struct sudo_conv_callback *callback) + { + char *pw_passwd = auth->data; +- int matched; ++ int ret; + debug_decl(sudo_passwd_verify, SUDOERS_DEBUG_AUTH); + + /* Simple string compare for systems without crypt(). */ + matched = !strcmp(pass, pw_passwd); ++ if (strcmp(pass, pw_passwd) == 0) ++ ret = AUTH_SUCCESS; ++ else ++ ret = AUTH_FAILURE; + +- debug_return_int(matched ? AUTH_SUCCESS : AUTH_FAILURE); ++ debug_return_int(ret); + } + #endif + +diff -up ./plugins/sudoers/auth/sudo_auth.c.rowhammer ./plugins/sudoers/auth/sudo_auth.c +--- ./plugins/sudoers/auth/sudo_auth.c.rowhammer 2020-12-17 02:33:43.000000000 +0100 ++++ ./plugins/sudoers/auth/sudo_auth.c 2024-01-22 16:01:16.331874669 +0100 +@@ -112,10 +112,16 @@ sudo_auth_init(struct passwd *pw) + if (auth->init && !IS_DISABLED(auth)) { + /* Disable if it failed to init unless there was a fatal error. */ + status = (auth->init)(pw, auth); +- if (status == AUTH_FAILURE) +- SET(auth->flags, FLAG_DISABLED); +- else if (status == AUTH_FATAL) +- break; /* assume error msg already printed */ ++ switch (status) { ++ case AUTH_SUCCESS: ++ break; ++ case AUTH_FAILURE: ++ SET(auth->flags, FLAG_DISABLED); ++ break; ++ default: ++ /* Assume error msg already printed. */ ++ debug_return_int(-1); ++ } + } + } + +@@ -161,7 +167,7 @@ sudo_auth_init(struct passwd *pw) + } + } + +- debug_return_int(status == AUTH_FATAL ? -1 : 0); ++ debug_return_int(0); + } + + /* +@@ -202,7 +208,7 @@ sudo_auth_cleanup(struct passwd *pw, boo + for (auth = auth_switch; auth->name; auth++) { + if (auth->cleanup && !IS_DISABLED(auth)) { + int status = (auth->cleanup)(pw, auth, force); +- if (status == AUTH_FATAL) { ++ if (status != AUTH_SUCCESS) { + /* Assume error msg already printed. */ + debug_return_int(-1); + } +@@ -297,7 +303,7 @@ verify_user(struct passwd *pw, char *pro + status = (auth->setup)(pw, &prompt, auth); + if (status == AUTH_FAILURE) + SET(auth->flags, FLAG_DISABLED); +- else if (status == AUTH_FATAL || user_interrupted()) ++ else if (status != AUTH_SUCCESS || user_interrupted()) + goto done; /* assume error msg already printed */ + } + } +@@ -348,7 +354,6 @@ done: + log_auth_failure(validated, ntries); + ret = false; + break; +- case AUTH_FATAL: + default: + log_auth_failure(validated, 0); + ret = -1; +@@ -360,24 +365,32 @@ done: + + /* + * Call authentication method begin session hooks. +- * Returns 1 on success and -1 on error. ++ * Returns true on success, false on failure and -1 on error. + */ + int + sudo_auth_begin_session(struct passwd *pw, char **user_env[]) + { + sudo_auth *auth; ++ int ret = true; + debug_decl(sudo_auth_begin_session, SUDOERS_DEBUG_AUTH); + + for (auth = auth_switch; auth->name; auth++) { + if (auth->begin_session && !IS_DISABLED(auth)) { + int status = (auth->begin_session)(pw, user_env, auth); +- if (status != AUTH_SUCCESS) { +- /* Assume error msg already printed. */ +- debug_return_int(-1); ++ switch (status) { ++ case AUTH_SUCCESS: ++ break; ++ case AUTH_FAILURE: ++ ret = false; ++ break; ++ default: ++ /* Assume error msg already printed. */ ++ ret = -1; ++ break; + } + } + } +- debug_return_int(1); ++ debug_return_int(ret); + } + + bool +@@ -398,25 +411,33 @@ sudo_auth_needs_end_session(void) + + /* + * Call authentication method end session hooks. +- * Returns 1 on success and -1 on error. ++ * Returns true on success, false on failure and -1 on error. + */ + int + sudo_auth_end_session(struct passwd *pw) + { + sudo_auth *auth; ++ int ret = true; + int status; + debug_decl(sudo_auth_end_session, SUDOERS_DEBUG_AUTH); + + for (auth = auth_switch; auth->name; auth++) { + if (auth->end_session && !IS_DISABLED(auth)) { + status = (auth->end_session)(pw, auth); +- if (status == AUTH_FATAL) { +- /* Assume error msg already printed. */ +- debug_return_int(-1); +- } ++ switch (status) { ++ case AUTH_SUCCESS: ++ break; ++ case AUTH_FAILURE: ++ ret = false; ++ break; ++ default: ++ /* Assume error msg already printed. */ ++ ret = -1; ++ break; ++ } + } + } +- debug_return_int(1); ++ debug_return_int(ret); + } + + /* +diff -up ./plugins/sudoers/auth/sudo_auth.h.rowhammer ./plugins/sudoers/auth/sudo_auth.h +--- ./plugins/sudoers/auth/sudo_auth.h.rowhammer 2020-12-17 02:33:43.000000000 +0100 ++++ ./plugins/sudoers/auth/sudo_auth.h 2024-01-22 16:01:16.332874679 +0100 +@@ -19,11 +19,11 @@ + #ifndef SUDO_AUTH_H + #define SUDO_AUTH_H + +-/* Auth function return values. */ +-#define AUTH_SUCCESS 0 +-#define AUTH_FAILURE 1 +-#define AUTH_INTR 2 +-#define AUTH_FATAL 3 ++/* Auth function return values (rowhammer resistent). */ ++#define AUTH_SUCCESS 0x52a2925 /* 0101001010100010100100100101 */ ++#define AUTH_FAILURE 0xad5d6da /* 1010110101011101011011011010 */ ++#define AUTH_INTR 0x69d61fc8 /* 1101001110101100001111111001000 */ ++#define AUTH_FATAL 0x1629e037 /* 0010110001010011110000000110111 */ + + typedef struct sudo_auth { + int flags; /* various flags, see below */ +diff -up ./plugins/sudoers/cvtsudoers.c.rowhammer ./plugins/sudoers/cvtsudoers.c +--- ./plugins/sudoers/cvtsudoers.c.rowhammer 2024-01-22 18:30:09.585081693 +0100 ++++ ./plugins/sudoers/cvtsudoers.c 2024-01-22 18:32:35.238519869 +0100 +@@ -685,7 +685,7 @@ userlist_matches_filter(struct sudoers_p + pw.pw_uid = (uid_t)-1; + pw.pw_gid = (gid_t)-1; + +- if (user_matches(parse_tree, &pw, m) == true) ++ if (user_matches(parse_tree, &pw, m) == ALLOW) + matched = true; + } else { + STAILQ_FOREACH(s, &filters->users, entries) { +@@ -711,7 +711,7 @@ userlist_matches_filter(struct sudoers_p + if (pw == NULL) + continue; + +- if (user_matches(parse_tree, pw, m) == true) ++ if (user_matches(parse_tree, pw, m) == ALLOW) + matched = true; + sudo_pw_delref(pw); + +@@ -787,7 +787,7 @@ hostlist_matches_filter(struct sudoers_p + + /* Only need one host in the filter to match. */ + /* XXX - can't use netgroup_tuple with NULL pw */ +- if (host_matches(parse_tree, NULL, lhost, shost, m) == true) { ++ if (host_matches(parse_tree, NULL, lhost, shost, m) == ALLOW) { + matched = true; + break; + } +diff -up ./plugins/sudoers/match.c.rowhammer ./plugins/sudoers/match.c +--- ./plugins/sudoers/match.c.rowhammer 2020-12-17 02:33:44.000000000 +0100 ++++ ./plugins/sudoers/match.c 2024-01-22 16:01:16.332874679 +0100 +@@ -26,6 +26,7 @@ + * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + */ + ++#include "parse.h" + #include + + #include +@@ -70,37 +71,42 @@ user_matches(struct sudoers_parse_tree * + { + const char *lhost = parse_tree->lhost ? parse_tree->lhost : user_runhost; + const char *shost = parse_tree->shost ? parse_tree->shost : user_srunhost; +- int matched = UNSPEC; ++ int rc, matched = UNSPEC; + struct alias *a; + debug_decl(user_matches, SUDOERS_DEBUG_MATCH); + + switch (m->type) { + case ALL: +- matched = !m->negated; ++ matched = m->negated ? DENY : ALLOW; + break; + case NETGROUP: + if (netgr_matches(m->name, + def_netgroup_tuple ? lhost : NULL, + def_netgroup_tuple ? shost : NULL, pw->pw_name)) +- matched = !m->negated; ++ matched = m->negated ? DENY : ALLOW; + break; + case USERGROUP: + if (usergr_matches(m->name, pw->pw_name, pw)) +- matched = !m->negated; ++ matched = m->negated ? DENY : ALLOW; + break; + case ALIAS: + if ((a = alias_get(parse_tree, m->name, USERALIAS)) != NULL) { + /* XXX */ +- int rc = userlist_matches(parse_tree, pw, &a->members); +- if (rc != UNSPEC) +- matched = m->negated ? !rc : rc; ++ rc = userlist_matches(parse_tree, pw, &a->members); ++ if (SPECIFIED(rc)) { ++ if (m->negated) { ++ matched = rc == ALLOW ? DENY : ALLOW; ++ } else { ++ matched = rc; ++ } ++ } + alias_put(a); + break; + } + FALLTHROUGH; + case WORD: + if (userpw_matches(m->name, pw->pw_name, pw)) +- matched = !m->negated; ++ matched = m->negated ? DENY : ALLOW; + break; + } + debug_return_int(matched); +@@ -119,7 +125,8 @@ userlist_matches(struct sudoers_parse_tr + debug_decl(userlist_matches, SUDOERS_DEBUG_MATCH); + + TAILQ_FOREACH_REVERSE(m, list, member_list, entries) { +- if ((matched = user_matches(parse_tree, pw, m)) != UNSPEC) ++ matched = user_matches(parse_tree, pw, m); ++ if (SPECIFIED(matched)) + break; + } + debug_return_int(matched); +@@ -164,48 +171,53 @@ runaslist_matches(struct sudoers_parse_t + /* If no runas user or runas group listed in sudoers, use default. */ + if (user_list == NULL && group_list == NULL) { + debug_return_int(userpw_matches(def_runas_default, +- runas_pw->pw_name, runas_pw)); ++ runas_pw->pw_name, runas_pw) ? ALLOW : DENY); + } + + if (user_list != NULL) { + TAILQ_FOREACH_REVERSE(m, user_list, member_list, entries) { + switch (m->type) { + case ALL: +- user_matched = !m->negated; ++ user_matched = m->negated ? DENY : ALLOW; + break; + case NETGROUP: + if (netgr_matches(m->name, + def_netgroup_tuple ? lhost : NULL, + def_netgroup_tuple ? shost : NULL, + runas_pw->pw_name)) +- user_matched = !m->negated; ++ user_matched = m->negated ? DENY : ALLOW; + break; + case USERGROUP: + if (usergr_matches(m->name, runas_pw->pw_name, runas_pw)) +- user_matched = !m->negated; ++ user_matched = m->negated ? DENY : ALLOW; + break; + case ALIAS: + a = alias_get(parse_tree, m->name, RUNASALIAS); + if (a != NULL) { + rc = runaslist_matches(parse_tree, &a->members, + &empty, matching_user, NULL); +- if (rc != UNSPEC) +- user_matched = m->negated ? !rc : rc; ++ if (SPECIFIED(rc)) { ++ if (m->negated) { ++ user_matched = rc == ALLOW ? DENY : ALLOW; ++ } else { ++ user_matched = rc; ++ } ++ } + alias_put(a); + break; + } + FALLTHROUGH; + case WORD: + if (userpw_matches(m->name, runas_pw->pw_name, runas_pw)) +- user_matched = !m->negated; ++ user_matched = m->negated ? DENY : ALLOW; + break; + case MYSELF: + if (!ISSET(sudo_user.flags, RUNAS_USER_SPECIFIED) || + strcmp(user_name, runas_pw->pw_name) == 0) +- user_matched = !m->negated; ++ user_matched = m->negated ? DENY : ALLOW; + break; + } +- if (user_matched != UNSPEC) { ++ if (SPECIFIED(user_matched)) { + if (matching_user != NULL && m->type != ALIAS) + *matching_user = m; + break; +@@ -226,34 +238,40 @@ runaslist_matches(struct sudoers_parse_t + TAILQ_FOREACH_REVERSE(m, group_list, member_list, entries) { + switch (m->type) { + case ALL: +- group_matched = !m->negated; ++ group_matched = m->negated ? DENY : ALLOW; + break; + case ALIAS: + a = alias_get(parse_tree, m->name, RUNASALIAS); + if (a != NULL) { + rc = runaslist_matches(parse_tree, &empty, + &a->members, NULL, matching_group); +- if (rc != UNSPEC) +- group_matched = m->negated ? !rc : rc; ++ if (SPECIFIED(rc)) { ++ if (m->negated) { ++ group_matched = rc == ALLOW ? DENY : ALLOW; ++ } else { ++ group_matched = rc; ++ } ++ } + alias_put(a); + break; + } + FALLTHROUGH; + case WORD: + if (group_matches(m->name, runas_gr)) +- group_matched = !m->negated; ++ group_matched = m->negated ? DENY : ALLOW; + break; + } +- if (group_matched != UNSPEC) { ++ if (SPECIFIED(group_matched)) { + if (matching_group != NULL && m->type != ALIAS) + *matching_group = m; + break; + } + } + } +- if (group_matched == UNSPEC) { ++ if (!SPECIFIED(group_matched)) { + struct gid_list *runas_groups; + /* ++ * + * The runas group was not explicitly allowed by sudoers. + * Check whether it is one of the target user's groups. + */ +@@ -295,7 +313,7 @@ hostlist_matches_int(struct sudoers_pars + + TAILQ_FOREACH_REVERSE(m, list, member_list, entries) { + matched = host_matches(parse_tree, pw, lhost, shost, m); +- if (matched != UNSPEC) ++ if (SPECIFIED(matched)) + break; + } + debug_return_int(matched); +@@ -324,37 +342,42 @@ host_matches(struct sudoers_parse_tree * + const char *lhost, const char *shost, const struct member *m) + { + struct alias *a; +- int matched = UNSPEC; ++ int rc, matched = UNSPEC; + debug_decl(host_matches, SUDOERS_DEBUG_MATCH); + + switch (m->type) { + case ALL: +- matched = !m->negated; ++ matched = m->negated ? DENY : ALLOW; + break; + case NETGROUP: + if (netgr_matches(m->name, lhost, shost, + def_netgroup_tuple ? pw->pw_name : NULL)) +- matched = !m->negated; ++ matched = m->negated ? DENY : ALLOW; + break; + case NTWKADDR: + if (addr_matches(m->name)) +- matched = !m->negated; ++ matched = m->negated ? DENY : ALLOW; + break; + case ALIAS: + a = alias_get(parse_tree, m->name, HOSTALIAS); + if (a != NULL) { + /* XXX */ +- int rc = hostlist_matches_int(parse_tree, pw, lhost, shost, ++ rc = hostlist_matches_int(parse_tree, pw, lhost, shost, + &a->members); +- if (rc != UNSPEC) +- matched = m->negated ? !rc : rc; ++ if (SPECIFIED(rc)) { ++ if (m->negated) { ++ matched = rc == ALLOW ? DENY : ALLOW; ++ } else { ++ matched = rc; ++ } ++ } + alias_put(a); + break; + } + FALLTHROUGH; + case WORD: + if (hostname_matches(shost, lhost, m->name)) +- matched = !m->negated; ++ matched = m->negated ? DENY : ALLOW; + break; + } + debug_return_int(matched); +@@ -375,7 +398,7 @@ cmndlist_matches(struct sudoers_parse_tr + + TAILQ_FOREACH_REVERSE(m, list, member_list, entries) { + matched = cmnd_matches(parse_tree, m, runchroot, info); +- if (matched != UNSPEC) ++ if (SPECIFIED(matched)) + break; + } + debug_return_int(matched); +@@ -397,21 +420,26 @@ cmnd_matches(struct sudoers_parse_tree * + switch (m->type) { + case ALL: + if (m->name == NULL) { +- matched = !m->negated; ++ matched = m->negated ? DENY : ALLOW; + break; + } + FALLTHROUGH; + case COMMAND: + c = (struct sudo_command *)m->name; + if (command_matches(c->cmnd, c->args, runchroot, info, &c->digests)) +- matched = !m->negated; ++ matched = m->negated ? DENY : ALLOW; + break; + case ALIAS: + a = alias_get(parse_tree, m->name, CMNDALIAS); + if (a != NULL) { + rc = cmndlist_matches(parse_tree, &a->members, runchroot, info); +- if (rc != UNSPEC) +- matched = m->negated ? !rc : rc; ++ if (SPECIFIED(rc)) { ++ if (m->negated) { ++ matched = rc == ALLOW ? DENY : ALLOW; ++ } else { ++ matched = rc; ++ } ++ } + alias_put(a); + } + break; +diff -up ./plugins/sudoers/parse.c.rowhammer ./plugins/sudoers/parse.c +--- ./plugins/sudoers/parse.c.rowhammer 2020-12-17 02:33:43.000000000 +0100 ++++ ./plugins/sudoers/parse.c 2024-01-22 16:01:16.333874689 +0100 +@@ -151,7 +151,7 @@ sudoers_lookup_check(struct sudo_nss *ns + if (runas_match == ALLOW) { + cmnd_match = cmnd_matches(nss->parse_tree, cs->cmnd, + cs->runchroot, info); +- if (cmnd_match != UNSPEC) { ++ if (SPECIFIED(cmnd_match)) { + /* + * If user is running command as himself, + * set runas_pw = sudo_user.pw. +@@ -365,7 +365,7 @@ sudoers_lookup(struct sudo_nss_list *snl + } + + m = sudoers_lookup_check(nss, pw, &validated, &info, &cs, &defs, now); +- if (m != UNSPEC) { ++ if (SPECIFIED(m)) { + match = m; + parse_tree = nss->parse_tree; + } +@@ -373,7 +373,7 @@ sudoers_lookup(struct sudo_nss_list *snl + if (!sudo_nss_can_continue(nss, m)) + break; + } +- if (match != UNSPEC) { ++ if (SPECIFIED(match)) { + if (info.cmnd_path != NULL) { + /* Update user_cmnd, user_stat, cmnd_status from matching entry. */ + free(user_cmnd); +diff -up ./plugins/sudoers/parse.h.rowhammer ./plugins/sudoers/parse.h +--- ./plugins/sudoers/parse.h.rowhammer 2021-01-09 21:12:16.000000000 +0100 ++++ ./plugins/sudoers/parse.h 2024-01-22 16:01:16.333874689 +0100 +@@ -20,6 +20,9 @@ + #ifndef SUDOERS_PARSE_H + #define SUDOERS_PARSE_H + ++#include ++#include ++#include + #include + #include "sudo_queue.h" + +@@ -31,13 +34,26 @@ + + #undef UNSPEC + #define UNSPEC -1 ++ ++/* Denied by policy (rowhammer resistent). */ + #undef DENY +-#define DENY 0 ++#define DENY 0xad5d6da /* 1010110101011101011011011010 */ ++ ++/* Allowed by policy (rowhammer resistent). */ + #undef ALLOW +-#define ALLOW 1 ++#define ALLOW 0x52a2925 /* 0101001010100010100100100101 */ ++ + #undef IMPLIED + #define IMPLIED 2 + ++ ++/* ++ * 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) ++ ++ + /* + * Initialize all tags to UNSPEC. + */ diff --git a/sudo.spec b/sudo.spec index 678ac5f..505b402 100644 --- a/sudo.spec +++ b/sudo.spec @@ -1,7 +1,7 @@ Summary: Allows restricted root access for specified users Name: sudo Version: 1.8.29 -Release: 10%{?dist} +Release: 11%{?dist} License: ISC Group: Applications/System URL: https://www.sudo.ws/ @@ -82,6 +82,8 @@ Patch23: sha-digest-calc.patch Patch24: sudo-1.9.12-CVE-2023-22809-whitelist.patch Patch25: sudo-1.9.12-CVE-2023-22809-backports.patch Patch26: sudo-1.9.12-CVE-2023-22809.patch +#https://github.com/sudo-project/sudo/commit/7873f8334c8d31031f8cfa83bd97ac6029309e4f +Patch27: sudo-1.9.15-CVE-2023-42465.patch %description Sudo (superuser do) allows a system administrator to give certain @@ -139,6 +141,7 @@ plugins that use %{name}. %patch24 -p1 -b .whitelist %patch25 -p1 -b .backports %patch26 -p1 -b .cve +%patch27 -p1 -b .rowhammer %build # Remove bundled copy of zlib @@ -298,6 +301,9 @@ rm -rf $RPM_BUILD_ROOT %{_mandir}/man8/sudo_plugin.8* %changelog +* Mon Feb 19 2024 Kaiqiang Wang - 1.8.29-11 +- Fix CVE-2023-42465 + * Wed Jan 11 2023 Radovan Sroka - 1.8.29.9 RHEL 8.8.0 ERRATUM - CVE-2023-22809 sudo: arbitrary file write with privileges of the RunAs user -- Gitee