sudo/backport-CVE-2023-42465.patch
2024-01-10 10:21:39 +08:00

443 lines
14 KiB
Diff

From 7873f8334c8d31031f8cfa83bd97ac6029309e4f Mon Sep 17 00:00:00 2001
From: "Todd C. Miller" <Todd.Miller@sudo.ws>
Date: Sat, 9 Sep 2023 14:07:04 -0600
Subject: [PATCH] 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
Reference: https://github.com/sudo-project/sudo/commit/7873f8334c8d31031f8cfa83bd97ac6029309e4f
Conflict: passwd.c sudo_auth.h match.c parse.c
---
plugins/sudoers/auth/passwd.c | 27 ++++++++++-------
plugins/sudoers/auth/sudo_auth.c | 51 ++++++++++++++++++++++----------
plugins/sudoers/auth/sudo_auth.h | 10 +++----
plugins/sudoers/match.c | 25 ++++++++--------
plugins/sudoers/parse.c | 6 ++--
plugins/sudoers/parse.h | 23 ++++++++++----
6 files changed, 92 insertions(+), 50 deletions(-)
diff --git a/plugins/sudoers/auth/passwd.c b/plugins/sudoers/auth/passwd.c
index bb9f2d6..8f163cb 100644
--- a/plugins/sudoers/auth/passwd.c
+++ b/plugins/sudoers/auth/passwd.c
@@ -62,7 +62,7 @@ sudo_passwd_verify(struct passwd *pw, char *pass, sudo_auth *auth, struct sudo_c
char des_pass[9], *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. */
@@ -74,7 +74,7 @@ sudo_passwd_verify(struct passwd *pw, char *pass, sudo_auth *auth, struct sudo_c
*/
pw_len = strlen(pw_epasswd);
if (pw_len == DESLEN || HAS_AGEINFO(pw_epasswd, pw_len)) {
- strlcpy(des_pass, pass, sizeof(des_pass));
+ (void)strlcpy(des_pass, pass, sizeof(des_pass));
pass = des_pass;
}
@@ -84,29 +84,36 @@ sudo_passwd_verify(struct passwd *pw, char *pass, sudo_auth *auth, struct sudo_c
* only compare the first DESLEN characters in that case.
*/
epass = (char *) crypt(pass, pw_epasswd);
+ 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;
+ }
}
explicit_bzero(des_pass, sizeof(des_pass));
- debug_return_int(matched ? AUTH_SUCCESS : AUTH_FAILURE);
+ 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 --git a/plugins/sudoers/auth/sudo_auth.c b/plugins/sudoers/auth/sudo_auth.c
index 188b65f..467233a 100644
--- a/plugins/sudoers/auth/sudo_auth.c
+++ b/plugins/sudoers/auth/sudo_auth.c
@@ -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)
+ switch (status) {
+ case AUTH_SUCCESS:
+ break;
+ case AUTH_FAILURE:
SET(auth->flags, FLAG_DISABLED);
- else if (status == AUTH_FATAL)
- break; /* assume error msg already printed */
+ 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, bool force)
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 *prompt, int validated,
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) {
+ switch (status) {
+ case AUTH_SUCCESS:
+ break;
+ case AUTH_FAILURE:
+ ret = false;
+ break;
+ default:
/* Assume error msg already printed. */
- debug_return_int(-1);
+ 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) {
+ switch (status) {
+ case AUTH_SUCCESS:
+ break;
+ case AUTH_FAILURE:
+ ret = false;
+ break;
+ default:
/* Assume error msg already printed. */
- debug_return_int(-1);
+ ret = -1;
+ break;
}
}
}
- debug_return_int(1);
+ debug_return_int(ret);
}
/*
diff --git a/plugins/sudoers/auth/sudo_auth.h b/plugins/sudoers/auth/sudo_auth.h
index 9ee408d..0b30d0c 100644
--- a/plugins/sudoers/auth/sudo_auth.h
+++ b/plugins/sudoers/auth/sudo_auth.h
@@ -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 --git a/plugins/sudoers/match.c b/plugins/sudoers/match.c
index 9324159..0891d63 100644
--- a/plugins/sudoers/match.c
+++ b/plugins/sudoers/match.c
@@ -92,7 +92,7 @@ user_matches(struct sudoers_parse_tree *parse_tree, const struct passwd *pw,
if ((a = alias_get(parse_tree, m->name, USERALIAS)) != NULL) {
/* XXX */
const int rc = userlist_matches(parse_tree, pw, &a->members);
- if (rc != UNSPEC) {
+ if (SPECIFIED(rc)) {
if (m->negated) {
matched = rc == ALLOW ? DENY : ALLOW;
} else {
@@ -124,7 +124,8 @@ userlist_matches(struct sudoers_parse_tree *parse_tree, const struct passwd *pw,
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);
@@ -194,7 +195,7 @@ runaslist_matches(struct sudoers_parse_tree *parse_tree,
if (a != NULL) {
rc = runaslist_matches(parse_tree, &a->members,
&empty, matching_user, NULL);
- if (rc != UNSPEC) {
+ if (SPECIFIED(rc)) {
if (m->negated) {
user_matched = rc == ALLOW ? DENY : ALLOW;
} else {
@@ -215,7 +216,7 @@ runaslist_matches(struct sudoers_parse_tree *parse_tree,
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;
@@ -228,7 +229,7 @@ runaslist_matches(struct sudoers_parse_tree *parse_tree,
* Skip checking runas group if none was specified.
*/
if (ISSET(sudo_user.flags, RUNAS_GROUP_SPECIFIED)) {
- if (user_matched == UNSPEC) {
+ if (!SPECIFIED(user_matched)) {
if (strcmp(user_name, runas_pw->pw_name) == 0)
user_matched = ALLOW; /* only changing group */
}
@@ -243,7 +244,7 @@ runaslist_matches(struct sudoers_parse_tree *parse_tree,
if (a != NULL) {
rc = runaslist_matches(parse_tree, &empty,
&a->members, NULL, matching_group);
- if (rc != UNSPEC) {
+ if (SPECIFIED(rc)) {
if (m->negated) {
group_matched = rc == ALLOW ? DENY : ALLOW;
} else {
@@ -259,14 +260,14 @@ runaslist_matches(struct sudoers_parse_tree *parse_tree,
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.
@@ -310,7 +311,7 @@ hostlist_matches_int(struct sudoers_parse_tree *parse_tree,
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);
@@ -361,7 +362,7 @@ host_matches(struct sudoers_parse_tree *parse_tree, const struct passwd *pw,
/* XXX */
const int rc = hostlist_matches_int(parse_tree, pw, lhost,
shost, &a->members);
- if (rc != UNSPEC) {
+ if (SPECIFIED(rc)) {
if (m->negated) {
matched = rc == ALLOW ? DENY : ALLOW;
} else {
@@ -395,7 +396,7 @@ cmndlist_matches(struct sudoers_parse_tree *parse_tree,
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);
@@ -425,7 +426,7 @@ cmnd_matches(struct sudoers_parse_tree *parse_tree, const struct member *m,
a = alias_get(parse_tree, m->name, CMNDALIAS);
if (a != NULL) {
rc = cmndlist_matches(parse_tree, &a->members, runchroot, info);
- if (rc != UNSPEC) {
+ if (SPECIFIED(rc)) {
if (m->negated) {
matched = rc == ALLOW ? DENY : ALLOW;
} else {
diff --git a/plugins/sudoers/parse.c b/plugins/sudoers/parse.c
index d3bf8a5..e38e7e9 100644
--- a/plugins/sudoers/parse.c
+++ b/plugins/sudoers/parse.c
@@ -153,7 +153,7 @@ sudoers_lookup_check(struct sudo_nss *nss, struct passwd *pw,
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.
@@ -396,7 +396,7 @@ sudoers_lookup(struct sudo_nss_list *snl, struct passwd *pw, int *cmnd_status,
}
m = sudoers_lookup_check(nss, pw, &validated, &info, &cs, &defs, now);
- if (m != UNSPEC) {
+ if (SPECIFIED(m)) {
match = m;
parse_tree = nss->parse_tree;
}
@@ -404,7 +404,7 @@ sudoers_lookup(struct sudo_nss_list *snl, struct passwd *pw, int *cmnd_status,
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 --git a/plugins/sudoers/parse.h b/plugins/sudoers/parse.h
index ea2a179..8559849 100644
--- a/plugins/sudoers/parse.h
+++ b/plugins/sudoers/parse.h
@@ -34,15 +34,28 @@
# define SUDOERS_NAME_MATCH
#endif
+/* 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. */
#undef UNSPEC
#define UNSPEC -1
-#undef DENY
-#define DENY 0
-#undef ALLOW
-#define ALLOW 1
+
+/* Tag implied by root access (SETENV only). */
#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.
*/
@@ -92,7 +105,7 @@
* Returns true if the specified tag is not UNSPEC or IMPLIED, else false.
*/
#define TAG_SET(tt) \
- ((tt) != UNSPEC && (tt) != IMPLIED)
+ ((tt) == true || (tt) == false)
/*
* Returns true if any tags set in nt differ between ot and nt, else false.
--
2.42.0.windows.2