From 9df355c5f1dceeba11c4d15aef3b41fb551ae6f3 Mon Sep 17 00:00:00 2001 From: Anuj Mittal Date: Sat, 6 Feb 2021 15:59:15 +0800 Subject: sudo: fix CVE-2021-23240 (From OE-Core rev: 98470df92dc8650c349cc454d5c11e12e6803f19) Signed-off-by: Anuj Mittal Signed-off-by: Richard Purdie --- .../sudo/files/CVE-2021-23240.patch | 419 +++++++++++++++++++++ meta/recipes-extended/sudo/sudo_1.9.3.bb | 1 + 2 files changed, 420 insertions(+) create mode 100644 meta/recipes-extended/sudo/files/CVE-2021-23240.patch diff --git a/meta/recipes-extended/sudo/files/CVE-2021-23240.patch b/meta/recipes-extended/sudo/files/CVE-2021-23240.patch new file mode 100644 index 0000000000..740a13cd90 --- /dev/null +++ b/meta/recipes-extended/sudo/files/CVE-2021-23240.patch @@ -0,0 +1,419 @@ +Upstream-Status: Backport [https://www.sudo.ws/repos/sudo/rev/8fcb36ef422a] +Signed-off-by: Anuj Mittal +CVE: CVE-2021-23240 + +# HG changeset patch +# User Todd C. Miller +# Date 1609953360 25200 +# Node ID 8fcb36ef422a251fe33738a347551439944a4a37 +# Parent ea19d0073c02951bbbf35342dd63304da83edce8 +Add security checks before using temp files for SELinux RBAC sudoedit. +Otherwise, it may be possible for the user running sudoedit to +replace the newly-created temporary files with a symbolic link and +have sudoedit set the owner of an arbitrary file. +Problem reported by Matthias Gerstner of SUSE. + +diff -r ea19d0073c02 -r 8fcb36ef422a src/copy_file.c +--- a/src/copy_file.c Wed Jan 06 10:16:00 2021 -0700 ++++ b/src/copy_file.c Wed Jan 06 10:16:00 2021 -0700 +@@ -1,7 +1,7 @@ + /* + * SPDX-License-Identifier: ISC + * +- * Copyright (c) 2020 Todd C. Miller ++ * Copyright (c) 2020-2021 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above +@@ -23,6 +23,8 @@ + + #include + ++#include ++ + #include + #include + #include +@@ -134,3 +136,34 @@ + sudo_warn(U_("unable to write to %s"), dst); + debug_return_int(-1); + } ++ ++#ifdef HAVE_SELINUX ++bool ++sudo_check_temp_file(int tfd, const char *tfile, uid_t uid, struct stat *sb) ++{ ++ struct stat sbuf; ++ debug_decl(sudo_check_temp_file, SUDO_DEBUG_UTIL); ++ ++ if (sb == NULL) ++ sb = &sbuf; ++ ++ if (fstat(tfd, sb) == -1) { ++ sudo_warn(U_("unable to stat %s"), tfile); ++ debug_return_bool(false); ++ } ++ if (!S_ISREG(sb->st_mode)) { ++ sudo_warnx(U_("%s: not a regular file"), tfile); ++ debug_return_bool(false); ++ } ++ if ((sb->st_mode & ALLPERMS) != (S_IRUSR|S_IWUSR)) { ++ sudo_warnx(U_("%s: bad file mode: 0%o"), tfile, sb->st_mode & ALLPERMS); ++ debug_return_bool(false); ++ } ++ if (sb->st_uid != uid) { ++ sudo_warnx(U_("%s is owned by uid %u, should be %u"), ++ tfile, (unsigned int)sb->st_uid, (unsigned int)uid); ++ debug_return_bool(false); ++ } ++ debug_return_bool(true); ++} ++#endif /* SELINUX */ +diff -r ea19d0073c02 -r 8fcb36ef422a src/sesh.c +--- a/src/sesh.c Wed Jan 06 10:16:00 2021 -0700 ++++ b/src/sesh.c Wed Jan 06 10:16:00 2021 -0700 +@@ -1,7 +1,7 @@ + /* + * SPDX-License-Identifier: ISC + * +- * Copyright (c) 2008, 2010-2018, 2020 Todd C. Miller ++ * Copyright (c) 2008, 2010-2018, 2020-2021 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above +@@ -132,7 +132,7 @@ + static int + sesh_sudoedit(int argc, char *argv[]) + { +- int i, oflags_dst, post, ret = SESH_ERR_FAILURE; ++ int i, oflags_src, oflags_dst, post, ret = SESH_ERR_FAILURE; + int fd_src = -1, fd_dst = -1, follow = 0; + struct stat sb; + struct timespec times[2]; +@@ -174,10 +174,12 @@ + debug_return_int(SESH_ERR_BAD_PATHS); + + /* +- * Use O_EXCL if we are not in the post editing stage +- * so that it's ensured that the temporary files are +- * created by us and that we are not opening any symlinks. ++ * In the pre-editing stage, use O_EXCL to ensure that the temporary ++ * files are created by us and that we are not opening any symlinks. ++ * In the post-editing stage, use O_NOFOLLOW so we don't follow symlinks ++ * when opening the temporary files. + */ ++ oflags_src = O_RDONLY|(post ? O_NONBLOCK|O_NOFOLLOW : follow); + oflags_dst = O_WRONLY|O_CREAT|(post ? follow : O_EXCL); + for (i = 0; i < argc - 1; i += 2) { + const char *path_src = argv[i]; +@@ -187,7 +189,7 @@ + * doesn't exist, that's OK, we'll create an empty + * destination file. + */ +- if ((fd_src = open(path_src, O_RDONLY|follow, S_IRUSR|S_IWUSR)) < 0) { ++ if ((fd_src = open(path_src, oflags_src, S_IRUSR|S_IWUSR)) < 0) { + if (errno != ENOENT) { + sudo_warn("%s", path_src); + if (post) { +@@ -197,6 +199,14 @@ + goto cleanup_0; + } + } ++ if (post) { ++ /* Make sure the temporary file is safe and has the proper owner. */ ++ if (!sudo_check_temp_file(fd_src, path_src, geteuid(), &sb)) { ++ ret = SESH_ERR_SOME_FILES; ++ goto nocleanup; ++ } ++ fcntl(fd_src, F_SETFL, fcntl(fd_src, F_GETFL, 0) & ~O_NONBLOCK); ++ } + + if ((fd_dst = open(path_dst, oflags_dst, post ? + (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) : (S_IRUSR|S_IWUSR))) < 0) { +@@ -214,10 +224,7 @@ + off_t len_dst = -1; + + if (post) { +- if (fstat(fd_src, &sb) != 0) { +- ret = SESH_ERR_SOME_FILES; +- goto nocleanup; +- } ++ /* sudo_check_temp_file() filled in sb for us. */ + len_src = sb.st_size; + if (fstat(fd_dst, &sb) != 0) { + ret = SESH_ERR_SOME_FILES; +diff -r ea19d0073c02 -r 8fcb36ef422a src/sudo_edit.c +--- a/src/sudo_edit.c Wed Jan 06 10:16:00 2021 -0700 ++++ b/src/sudo_edit.c Wed Jan 06 10:16:00 2021 -0700 +@@ -1,7 +1,7 @@ + /* + * SPDX-License-Identifier: ISC + * +- * Copyright (c) 2004-2008, 2010-2020 Todd C. Miller ++ * Copyright (c) 2004-2008, 2010-2021 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above +@@ -259,8 +259,10 @@ + } else { + len = asprintf(tfile, "%s/%s.XXXXXXXX", edit_tmpdir, cp); + } +- if (len == -1) +- sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory")); ++ if (len == -1) { ++ sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); ++ debug_return_int(-1); ++ } + tfd = mkstemps(*tfile, suff ? strlen(suff) : 0); + sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, + "%s -> %s, fd %d", ofile, *tfile, tfd); +@@ -735,7 +737,8 @@ + + #ifdef HAVE_SELINUX + static int +-selinux_run_helper(char *argv[], char *envp[]) ++selinux_run_helper(uid_t uid, gid_t gid, int ngroups, GETGROUPS_T *groups, ++ char *const argv[], char *const envp[]) + { + int status, ret = SESH_ERR_FAILURE; + const char *sesh; +@@ -755,8 +758,10 @@ + break; + case 0: + /* child runs sesh in new context */ +- if (selinux_setcon() == 0) ++ if (selinux_setcon() == 0) { ++ switch_user(uid, gid, ngroups, groups); + execve(sesh, argv, envp); ++ } + _exit(SESH_ERR_FAILURE); + default: + /* parent waits */ +@@ -775,7 +780,7 @@ + struct tempfile *tf, char *files[], int nfiles) + { + char **sesh_args, **sesh_ap; +- int i, rc, sesh_nargs; ++ int i, error, sesh_nargs, ret = -1; + struct stat sb; + debug_decl(selinux_edit_create_tfiles, SUDO_DEBUG_EDIT); + +@@ -787,7 +792,7 @@ + sesh_args = sesh_ap = reallocarray(NULL, sesh_nargs, sizeof(char *)); + if (sesh_args == NULL) { + sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); +- debug_return_int(-1); ++ goto done; + } + *sesh_ap++ = "sesh"; + *sesh_ap++ = "-e"; +@@ -795,7 +800,6 @@ + *sesh_ap++ = "-h"; + *sesh_ap++ = "0"; + +- /* XXX - temp files should be created with user's context */ + for (i = 0; i < nfiles; i++) { + char *tfile, *ofile = files[i]; + int tfd; +@@ -813,8 +817,7 @@ + if (tfd == -1) { + sudo_warn("mkstemps"); + free(tfile); +- free(sesh_args); +- debug_return_int(-1); ++ goto done; + } + /* Helper will re-create temp file with proper security context. */ + close(tfd); +@@ -825,8 +828,10 @@ + *sesh_ap = NULL; + + /* Run sesh -e [-h] 0 ... */ +- rc = selinux_run_helper(sesh_args, command_details->envp); +- switch (rc) { ++ error = selinux_run_helper(command_details->uid, command_details->gid, ++ command_details->ngroups, command_details->groups, sesh_args, ++ command_details->envp); ++ switch (error) { + case SESH_SUCCESS: + break; + case SESH_ERR_BAD_PATHS: +@@ -836,21 +841,35 @@ + case SESH_ERR_KILLED: + sudo_fatalx("%s", U_("sesh: killed by a signal")); + default: +- sudo_fatalx(U_("sesh: unknown error %d"), rc); ++ sudo_warnx(U_("sesh: unknown error %d"), error); ++ goto done; + } + +- /* Chown to user's UID so they can edit the temporary files. */ + for (i = 0; i < nfiles; i++) { +- if (chown(tf[i].tfile, user_details.uid, user_details.gid) != 0) { ++ int tfd = open(tf[i].tfile, O_RDONLY|O_NONBLOCK|O_NOFOLLOW); ++ if (tfd == -1) { ++ sudo_warn(U_("unable to open %s"), tf[i].tfile); ++ goto done; ++ } ++ if (!sudo_check_temp_file(tfd, tf[i].tfile, command_details->uid, NULL)) { ++ close(tfd); ++ goto done; ++ } ++ if (fchown(tfd, user_details.uid, user_details.gid) != 0) { + sudo_warn("unable to chown(%s) to %d:%d for editing", + tf[i].tfile, user_details.uid, user_details.gid); ++ close(tfd); ++ goto done; + } ++ close(tfd); + } ++ ret = nfiles; + ++done: + /* Contents of tf will be freed by caller. */ + free(sesh_args); + +- return (nfiles); ++ debug_return_int(ret); + } + + static int +@@ -858,7 +877,8 @@ + struct tempfile *tf, int nfiles, struct timespec *times) + { + char **sesh_args, **sesh_ap; +- int i, rc, sesh_nargs, ret = 1; ++ int i, error, sesh_nargs, ret = 1; ++ int tfd = -1; + struct timespec ts; + struct stat sb; + debug_decl(selinux_edit_copy_tfiles, SUDO_DEBUG_EDIT); +@@ -879,33 +899,43 @@ + + /* Construct args for sesh -e 1 */ + for (i = 0; i < nfiles; i++) { +- if (stat(tf[i].tfile, &sb) == 0) { +- mtim_get(&sb, ts); +- if (tf[i].osize == sb.st_size && sudo_timespeccmp(&tf[i].omtim, &ts, ==)) { +- /* +- * If mtime and size match but the user spent no measurable +- * time in the editor we can't tell if the file was changed. +- */ +- if (sudo_timespeccmp(×[0], ×[1], !=)) { +- sudo_warnx(U_("%s unchanged"), tf[i].ofile); +- unlink(tf[i].tfile); +- continue; +- } ++ if (tfd != -1) ++ close(tfd); ++ if ((tfd = open(tf[i].tfile, O_RDONLY|O_NONBLOCK|O_NOFOLLOW)) == -1) { ++ sudo_warn(U_("unable to open %s"), tf[i].tfile); ++ continue; ++ } ++ if (!sudo_check_temp_file(tfd, tf[i].tfile, user_details.uid, &sb)) ++ continue; ++ mtim_get(&sb, ts); ++ if (tf[i].osize == sb.st_size && sudo_timespeccmp(&tf[i].omtim, &ts, ==)) { ++ /* ++ * If mtime and size match but the user spent no measurable ++ * time in the editor we can't tell if the file was changed. ++ */ ++ if (sudo_timespeccmp(×[0], ×[1], !=)) { ++ sudo_warnx(U_("%s unchanged"), tf[i].ofile); ++ unlink(tf[i].tfile); ++ continue; + } + } + *sesh_ap++ = tf[i].tfile; + *sesh_ap++ = tf[i].ofile; +- if (chown(tf[i].tfile, command_details->uid, command_details->gid) != 0) { ++ if (fchown(tfd, command_details->uid, command_details->gid) != 0) { + sudo_warn("unable to chown(%s) back to %d:%d", tf[i].tfile, + command_details->uid, command_details->gid); + } + } + *sesh_ap = NULL; ++ if (tfd != -1) ++ close(tfd); + + if (sesh_ap - sesh_args > 3) { + /* Run sesh -e 1 ... */ +- rc = selinux_run_helper(sesh_args, command_details->envp); +- switch (rc) { ++ error = selinux_run_helper(command_details->uid, command_details->gid, ++ command_details->ngroups, command_details->groups, sesh_args, ++ command_details->envp); ++ switch (error) { + case SESH_SUCCESS: + ret = 0; + break; +@@ -921,7 +951,7 @@ + sudo_warnx("%s", U_("sesh: killed by a signal")); + break; + default: +- sudo_warnx(U_("sesh: unknown error %d"), rc); ++ sudo_warnx(U_("sesh: unknown error %d"), error); + break; + } + if (ret != 0) +@@ -943,7 +973,7 @@ + { + struct command_details saved_command_details; + char **nargv = NULL, **ap, **files = NULL; +- int errors, i, ac, nargc, rc; ++ int errors, i, ac, nargc, ret; + int editor_argc = 0, nfiles = 0; + struct timespec times[2]; + struct tempfile *tf = NULL; +@@ -1038,7 +1068,7 @@ + command_details->ngroups = user_details.ngroups; + command_details->groups = user_details.groups; + command_details->argv = nargv; +- rc = run_command(command_details); ++ ret = run_command(command_details); + if (sudo_gettime_real(×[1]) == -1) { + sudo_warn("%s", U_("unable to read the clock")); + goto cleanup; +@@ -1062,14 +1092,14 @@ + errors = sudo_edit_copy_tfiles(command_details, tf, nfiles, times); + if (errors) { + /* Preserve the edited temporary files. */ +- rc = W_EXITCODE(1, 0); ++ ret = W_EXITCODE(1, 0); + } + + for (i = 0; i < nfiles; i++) + free(tf[i].tfile); + free(tf); + free(nargv); +- debug_return_int(rc); ++ debug_return_int(ret); + + cleanup: + /* Clean up temp files and return. */ +diff -r ea19d0073c02 -r 8fcb36ef422a src/sudo_exec.h +--- a/src/sudo_exec.h Wed Jan 06 10:16:00 2021 -0700 ++++ b/src/sudo_exec.h Wed Jan 06 10:16:00 2021 -0700 +@@ -1,7 +1,7 @@ + /* + * SPDX-License-Identifier: ISC + * +- * Copyright (c) 2010-2016 Todd C. Miller ++ * Copyright (c) 2010-2017, 2020-2021 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above +@@ -84,9 +84,11 @@ + */ + struct command_details; + struct command_status; ++struct stat; + + /* copy_file.c */ + int sudo_copy_file(const char *src, int src_fd, off_t src_len, const char *dst, int dst_fd, off_t dst_len); ++bool sudo_check_temp_file(int tfd, const char *tname, uid_t uid, struct stat *sb); + + /* exec.c */ + void exec_cmnd(struct command_details *details, int errfd); + + diff --git a/meta/recipes-extended/sudo/sudo_1.9.3.bb b/meta/recipes-extended/sudo/sudo_1.9.3.bb index 132d9a8cb9..4edcbfc607 100644 --- a/meta/recipes-extended/sudo/sudo_1.9.3.bb +++ b/meta/recipes-extended/sudo/sudo_1.9.3.bb @@ -4,6 +4,7 @@ SRC_URI = "https://www.sudo.ws/dist/sudo-${PV}.tar.gz \ ${@bb.utils.contains('DISTRO_FEATURES', 'pam', '${PAM_SRC_URI}', '', d)} \ file://0001-sudo.conf.in-fix-conflict-with-multilib.patch \ file://CVE-2021-23239.patch \ + file://CVE-2021-23240.patch \ " PAM_SRC_URI = "file://sudo.pam" -- cgit v1.2.3-54-g00ecf