From patchwork Tue May 7 11:47:11 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Anthony Liguori X-Patchwork-Id: 242185 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id 7FE0C2C019E for ; Tue, 7 May 2013 21:47:53 +1000 (EST) Received: from localhost ([::1]:53863 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1UZgN5-0004z9-Ir for incoming@patchwork.ozlabs.org; Tue, 07 May 2013 07:47:51 -0400 Received: from eggs.gnu.org ([208.118.235.92]:39488) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1UZgMk-0004yz-DW for qemu-devel@nongnu.org; Tue, 07 May 2013 07:47:32 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1UZgMi-0003Sv-F3 for qemu-devel@nongnu.org; Tue, 07 May 2013 07:47:30 -0400 Received: from e23smtp03.au.ibm.com ([202.81.31.145]:33083) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1UZgMh-0003OP-LF for qemu-devel@nongnu.org; Tue, 07 May 2013 07:47:28 -0400 Received: from /spool/local by e23smtp03.au.ibm.com with IBM ESMTP SMTP Gateway: Authorized Use Only! Violators will be prosecuted for from ; Tue, 7 May 2013 21:39:14 +1000 Received: from d23dlp03.au.ibm.com (202.81.31.214) by e23smtp03.au.ibm.com (202.81.31.209) with IBM ESMTP SMTP Gateway: Authorized Use Only! Violators will be prosecuted; Tue, 7 May 2013 21:39:13 +1000 Received: from d23relay05.au.ibm.com (d23relay05.au.ibm.com [9.190.235.152]) by d23dlp03.au.ibm.com (Postfix) with ESMTP id E43D83578017 for ; Tue, 7 May 2013 21:47:18 +1000 (EST) Received: from d23av03.au.ibm.com (d23av03.au.ibm.com [9.190.234.97]) by d23relay05.au.ibm.com (8.13.8/8.13.8/NCO v10.0) with ESMTP id r47BXInU18612294 for ; Tue, 7 May 2013 21:33:19 +1000 Received: from d23av03.au.ibm.com (loopback [127.0.0.1]) by d23av03.au.ibm.com (8.14.4/8.13.1/NCO v10.0 AVout) with ESMTP id r47BlHe9015121 for ; Tue, 7 May 2013 21:47:17 +1000 Received: from localhost6.localdomain6 (sig-9-65-126-115.mts.ibm.com [9.65.126.115]) by d23av03.au.ibm.com (8.14.4/8.13.1/NCO v10.0 AVin) with ESMTP id r47BlCLr014878; Tue, 7 May 2013 21:47:15 +1000 From: Anthony Liguori To: qemu-devel@nongnu.org Date: Tue, 7 May 2013 06:47:11 -0500 Message-Id: <1367927231-24291-1-git-send-email-aliguori@us.ibm.com> X-Mailer: git-send-email 1.8.0 X-Content-Scanned: Fidelis XPS MAILER x-cbid: 13050711-6102-0000-0000-0000037640C4 X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.4.x-2.6.x [generic] X-Received-From: 202.81.31.145 Cc: Anthony Liguori , Laszlo Ersek Subject: [Qemu-devel] [PATCH] qga: set umask 0077 when daemonizing (CVE-2013-2007) X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org From: Laszlo Ersek The qemu guest agent creates a bunch of files with insecure permissions when started in daemon mode. For example: -rw-rw-rw- 1 root root /var/log/qemu-ga.log -rw-rw-rw- 1 root root /var/run/qga.state -rw-rw-rw- 1 root root /var/log/qga-fsfreeze-hook.log In addition, at least all files created with the "guest-file-open" QMP command, and all files created with shell output redirection (or otherwise) by utilities invoked by the fsfreeze hook script are affected. For now mask all file mode bits for "group" and "others" in become_daemon(). Temporarily, for compatibility reasons, stick with the 0666 file-mode in case of files newly created by the "guest-file-open" QMP call. Do so without changing the umask temporarily. Signed-off-by: Laszlo Ersek Signed-off-by: Anthony Liguori --- qga/commands-posix.c | 123 +++++++++++++++++++++++++++++++++++++++++++++++++-- qga/main.c | 2 +- 2 files changed, 120 insertions(+), 5 deletions(-) diff --git a/qga/commands-posix.c b/qga/commands-posix.c index 3b5c536..04c6951 100644 --- a/qga/commands-posix.c +++ b/qga/commands-posix.c @@ -18,6 +18,9 @@ #include #include #include +#include +#include +#include #include #include "qga/guest-agent-core.h" #include "qga-qmp-commands.h" @@ -237,9 +240,122 @@ static GuestFileHandle *guest_file_handle_find(int64_t id, Error **err) return NULL; } +typedef const char * const ccpc; + +/* http://pubs.opengroup.org/onlinepubs/9699919799/functions/fopen.html */ +static const struct { + ccpc *forms; + int oflag_base; +} guest_file_open_modes[] = { + { (ccpc[]){ "r", "rb", NULL }, O_RDONLY }, + { (ccpc[]){ "w", "wb", NULL }, O_WRONLY | O_CREAT | O_TRUNC }, + { (ccpc[]){ "a", "ab", NULL }, O_WRONLY | O_CREAT | O_APPEND }, + { (ccpc[]){ "r+", "rb+", "r+b", NULL }, O_RDWR }, + { (ccpc[]){ "w+", "wb+", "w+b", NULL }, O_RDWR | O_CREAT | O_TRUNC }, + { (ccpc[]){ "a+", "ab+", "a+b", NULL }, O_RDWR | O_CREAT | O_APPEND } +}; + +static int +find_open_flag(const char *mode_str, Error **err) +{ + unsigned mode; + + for (mode = 0; mode < ARRAY_SIZE(guest_file_open_modes); ++mode) { + ccpc *form; + + form = guest_file_open_modes[mode].forms; + while (*form != NULL && strcmp(*form, mode_str) != 0) { + ++form; + } + if (*form != NULL) { + break; + } + } + + if (mode == ARRAY_SIZE(guest_file_open_modes)) { + error_setg(err, "invalid file open mode '%s'", mode_str); + return -1; + } + return guest_file_open_modes[mode].oflag_base | O_NOCTTY | O_NONBLOCK; +} + +#define DEFAULT_NEW_FILE_MODE (S_IRUSR | S_IWUSR | \ + S_IRGRP | S_IWGRP | \ + S_IROTH | S_IWOTH) + +static FILE * +safe_open_or_create(const char *path, const char *mode, Error **err) +{ + Error *local_err = NULL; + int oflag; + + oflag = find_open_flag(mode, &local_err); + if (local_err == NULL) { + int fd; + + /* If the caller wants / allows creation of a new file, we implement it + * with a two step process: open() + (open() / fchmod()). + * + * First we insist on creating the file exclusively as a new file. If + * that succeeds, we're free to set any file-mode bits on it. (The + * motivation is that we want to set those file-mode bits independently + * of the current umask.) + * + * If the exclusive creation fails because the file already exists + * (EEXIST is not possible for any other reason), we just attempt to + * open the file, but in this case we won't be allowed to change the + * file-mode bits on the preexistent file. + * + * The pathname should never disappear between the two open()s in + * practice. If it happens, then someone very likely tried to race us. + * In this case just go ahead and report the ENOENT from the second + * open() to the caller. + * + * If the caller wants to open a preexistent file, then the first + * open() is decisive and its third argument is ignored, and the second + * open() and the fchmod() are never called. + */ + fd = open(path, oflag | ((oflag & O_CREAT) ? O_EXCL : 0), 0); + if (fd == -1 && errno == EEXIST) { + oflag &= ~(unsigned)O_CREAT; + fd = open(path, oflag); + } + + if (fd == -1) { + error_setg_errno(&local_err, errno, "failed to open file '%s' " + "(mode: '%s')", path, mode); + } else { + qemu_set_cloexec(fd); + + if ((oflag & O_CREAT) && fchmod(fd, DEFAULT_NEW_FILE_MODE) == -1) { + error_setg_errno(&local_err, errno, "failed to set permission " + "0%03o on new file '%s' (mode: '%s')", + (unsigned)DEFAULT_NEW_FILE_MODE, path, mode); + } else { + FILE *f; + + f = fdopen(fd, mode); + if (f == NULL) { + error_setg_errno(&local_err, errno, "failed to associate " + "stdio stream with file descriptor %d, " + "file '%s' (mode: '%s')", fd, path, mode); + } else { + return f; + } + } + + close(fd); + } + } + + error_propagate(err, local_err); + return NULL; +} + int64_t qmp_guest_file_open(const char *path, bool has_mode, const char *mode, Error **err) { FILE *fh; + Error *local_err = NULL; int fd; int64_t ret = -1, handle; @@ -247,10 +363,9 @@ int64_t qmp_guest_file_open(const char *path, bool has_mode, const char *mode, E mode = "r"; } slog("guest-file-open called, filepath: %s, mode: %s", path, mode); - fh = fopen(path, mode); - if (!fh) { - error_setg_errno(err, errno, "failed to open file '%s' (mode: '%s')", - path, mode); + fh = safe_open_or_create(path, mode, &local_err); + if (local_err != NULL) { + error_propagate(err, local_err); return -1; } diff --git a/qga/main.c b/qga/main.c index 1841759..44a2836 100644 --- a/qga/main.c +++ b/qga/main.c @@ -478,7 +478,7 @@ static void become_daemon(const char *pidfile) } } - umask(0); + umask(S_IRWXG | S_IRWXO); sid = setsid(); if (sid < 0) { goto fail;