From nobody Tue Feb 10 09:59:34 2026 Delivered-To: importer@patchew.org Received-SPF: pass (zoho.com: domain of gnu.org designates 208.118.235.17 as permitted sender) client-ip=208.118.235.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Authentication-Results: mx.zoho.com; spf=pass (zoho.com: domain of gnu.org designates 208.118.235.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; Return-Path: Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) by mx.zohomail.com with SMTPS id 1487602314317902.3451876542514; Mon, 20 Feb 2017 06:51:54 -0800 (PST) Received: from localhost ([::1]:38989 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1cfpJf-0007UH-Vh for importer@patchew.org; Mon, 20 Feb 2017 09:51:52 -0500 Received: from eggs.gnu.org ([2001:4830:134:3::10]:54005) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1cfp8f-0006Kl-W7 for qemu-devel@nongnu.org; Mon, 20 Feb 2017 09:40:31 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1cfp8c-0003df-Pf for qemu-devel@nongnu.org; Mon, 20 Feb 2017 09:40:30 -0500 Received: from mx0a-001b2d01.pphosted.com ([148.163.156.1]:42210) by eggs.gnu.org with esmtps (TLS1.0:RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1cfp8c-0003dK-FM for qemu-devel@nongnu.org; Mon, 20 Feb 2017 09:40:26 -0500 Received: from pps.filterd (m0098404.ppops.net [127.0.0.1]) by mx0a-001b2d01.pphosted.com (8.16.0.20/8.16.0.20) with SMTP id v1KEd4A0025271 for ; Mon, 20 Feb 2017 09:40:25 -0500 Received: from e36.co.us.ibm.com (e36.co.us.ibm.com [32.97.110.154]) by mx0a-001b2d01.pphosted.com with ESMTP id 28qvbyfpdn-1 (version=TLSv1.2 cipher=AES256-SHA bits=256 verify=NOT) for ; Mon, 20 Feb 2017 09:40:25 -0500 Received: from localhost by e36.co.us.ibm.com with IBM ESMTP SMTP Gateway: Authorized Use Only! Violators will be prosecuted for from ; Mon, 20 Feb 2017 07:40:23 -0700 Received: from d03dlp01.boulder.ibm.com (9.17.202.177) by e36.co.us.ibm.com (192.168.1.136) with IBM ESMTP SMTP Gateway: Authorized Use Only! Violators will be prosecuted; Mon, 20 Feb 2017 07:40:19 -0700 Received: from b01cxnp22035.gho.pok.ibm.com (b01cxnp22035.gho.pok.ibm.com [9.57.198.25]) by d03dlp01.boulder.ibm.com (Postfix) with ESMTP id 33D021FF002B; Mon, 20 Feb 2017 07:39:56 -0700 (MST) Received: from b01ledav001.gho.pok.ibm.com (b01ledav001.gho.pok.ibm.com [9.57.199.106]) by b01cxnp22035.gho.pok.ibm.com (8.14.9/8.14.9/NCO v10.0) with ESMTP id v1KEeIcA56950952; Mon, 20 Feb 2017 14:40:18 GMT Received: from b01ledav001.gho.pok.ibm.com (unknown [127.0.0.1]) by IMSVA (Postfix) with ESMTP id A21122803E; Mon, 20 Feb 2017 09:40:17 -0500 (EST) Received: from bahia.lan (unknown [9.164.137.25]) by b01ledav001.gho.pok.ibm.com (Postfix) with ESMTP id 4F1D228041; Mon, 20 Feb 2017 09:40:16 -0500 (EST) From: Greg Kurz To: qemu-devel@nongnu.org Date: Mon, 20 Feb 2017 15:40:15 +0100 In-Reply-To: <148760155821.31154.13876757160410915057.stgit@bahia.lan> References: <148760155821.31154.13876757160410915057.stgit@bahia.lan> User-Agent: StGit/0.17.1-20-gc0b1b-dirty MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable X-TM-AS-GCONF: 00 X-Content-Scanned: Fidelis XPS MAILER x-cbid: 17022014-0020-0000-0000-00000B6C1393 X-IBM-SpamModules-Scores: X-IBM-SpamModules-Versions: BY=3.00006651; HX=3.00000240; KW=3.00000007; PH=3.00000004; SC=3.00000204; SDB=6.00824568; UDB=6.00403651; IPR=6.00602025; BA=6.00005157; NDR=6.00000001; ZLA=6.00000005; ZF=6.00000009; ZB=6.00000000; ZP=6.00000000; ZH=6.00000000; ZU=6.00000002; MB=3.00014360; XFM=3.00000011; UTC=2017-02-20 14:40:21 X-IBM-AV-DETECTION: SAVI=unused REMOTE=unused XFE=unused x-cbparentid: 17022014-0021-0000-0000-00005A431FFD Message-Id: <148760161575.31154.505252736798591155.stgit@bahia.lan> X-Proofpoint-Virus-Version: vendor=fsecure engine=2.50.10432:, , definitions=2017-02-20_13:, , signatures=0 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 spamscore=0 suspectscore=3 malwarescore=0 phishscore=0 adultscore=0 bulkscore=0 classifier=spam adjust=0 reason=mlx scancount=1 engine=8.0.1-1612050000 definitions=main-1702200144 X-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x [generic] [fuzzy] X-Received-From: 148.163.156.1 Subject: [Qemu-devel] [PATCH 07/29] 9pfs: local: introduce symlink-attack safe xattr helpers X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Jann Horn , Prasad J Pandit , Greg Kurz , "Aneesh Kumar K.V" , Stefan Hajnoczi Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: "Qemu-devel" X-ZohoMail: RSF_0 Z_629925259 SPT_0 All operations dealing with extended attributes are vulnerable to symlink attacks because they use path-based syscalls which can traverse symbolic links while walking through the dirname part of the path. The solution is to introduce helpers based on opendir_nofollow(). This calls for "at" versions of the extended attribute syscalls, which don't exist unfortunately. This patch implement them by simulating the "at" behavior with fchdir(). Since the current working directory is process wide, and we don't want to confuse another thread in QEMU, all the work is done in a separate process. The extended attributes code spreads over several files: all helpers are hence declared with external linkage in 9p-xattr.h. Note that the listxattr-based code is fully contained in 9p-xattr.c: the flistxattrat_nofollow() helper is added in a subsequent patch. Signed-off-by: Greg Kurz --- hw/9pfs/9p-xattr.c | 158 ++++++++++++++++++++++++++++++++++++++++++++++++= ++++ hw/9pfs/9p-xattr.h | 13 ++++ 2 files changed, 171 insertions(+) diff --git a/hw/9pfs/9p-xattr.c b/hw/9pfs/9p-xattr.c index 19a2daf02f5c..62993624ff64 100644 --- a/hw/9pfs/9p-xattr.c +++ b/hw/9pfs/9p-xattr.c @@ -15,7 +15,165 @@ #include "9p.h" #include "fsdev/file-op-9p.h" #include "9p-xattr.h" +#include "9p-util.h" =20 +enum { + XATTRAT_OP_GET =3D 0, + XATTRAT_OP_LIST, + XATTRAT_OP_SET, + XATTRAT_OP_REMOVE +}; + +struct xattrat_data { + ssize_t ret; + int serrno; + char value[0]; +}; + +static void munmap_preserver_errno(void *addr, size_t length) +{ + int serrno =3D errno; + munmap(addr, length); + errno =3D serrno; +} + +static ssize_t do_xattrat_op(int op_type, int dirfd, const char *path, + const char *name, void *value, size_t size, + int flags) +{ + struct xattrat_data *data; + pid_t pid; + ssize_t ret =3D -1; + int wstatus; + + data =3D mmap(NULL, sizeof(*data) + size, PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANONYMOUS, -1, 0); + if (data =3D=3D MAP_FAILED) { + return -1; + } + data->ret =3D -1; + + pid =3D fork(); + if (pid < 0) { + goto err_out; + } else if (pid =3D=3D 0) { + if (fchdir(dirfd) =3D=3D 0) { + switch (op_type) { + case XATTRAT_OP_GET: + data->ret =3D lgetxattr(path, name, data->value, size); + break; + case XATTRAT_OP_LIST: + data->ret =3D llistxattr(path, data->value, size); + break; + case XATTRAT_OP_SET: + data->ret =3D lsetxattr(path, name, value, size, flags); + break; + case XATTRAT_OP_REMOVE: + data->ret =3D lremovexattr(path, name); + break; + default: + g_assert_not_reached(); + } + } + data->serrno =3D errno; + _exit(0); + } + assert(waitpid(pid, &wstatus, 0) =3D=3D pid && WIFEXITED(wstatus)); + + ret =3D data->ret; + if (ret < 0) { + errno =3D data->serrno; + goto err_out; + } + if (value) { + memcpy(value, data->value, data->ret); + } +err_out: + munmap_preserver_errno(data, sizeof(*data) + size); + return ret; +} + +ssize_t fgetxattrat_nofollow(int dirfd, const char *path, const char *name, + void *value, size_t size) +{ + return do_xattrat_op(XATTRAT_OP_GET, dirfd, path, name, value, size, 0= ); +} + +ssize_t local_getxattr_nofollow(FsContext *ctx, const char *path, + const char *name, void *value, size_t size) +{ + char *dirpath =3D g_path_get_dirname(path); + char *filename =3D g_path_get_basename(path); + int dirfd; + ssize_t ret =3D -1; + + dirfd =3D local_opendir_nofollow(ctx, dirpath); + if (dirfd =3D=3D -1) { + goto out; + } + + ret =3D fgetxattrat_nofollow(dirfd, filename, name, value, size); + close_preserve_errno(dirfd); +out: + g_free(dirpath); + g_free(filename); + return ret; +} + +int fsetxattrat_nofollow(int dirfd, const char *path, const char *name, + void *value, size_t size, int flags) +{ + return do_xattrat_op(XATTRAT_OP_SET, dirfd, path, name, value, size, f= lags); +} + +ssize_t local_setxattr_nofollow(FsContext *ctx, const char *path, + const char *name, void *value, size_t size, + int flags) +{ + char *dirpath =3D g_path_get_dirname(path); + char *filename =3D g_path_get_basename(path); + int dirfd; + ssize_t ret =3D -1; + + dirfd =3D local_opendir_nofollow(ctx, dirpath); + if (dirfd =3D=3D -1) { + goto out; + } + + ret =3D fsetxattrat_nofollow(dirfd, filename, name, value, size, flags= ); + close_preserve_errno(dirfd); +out: + g_free(dirpath); + g_free(filename); + return ret; +} + +static ssize_t fremovexattrat_nofollow(int dirfd, const char *path, + const char *name) +{ + return do_xattrat_op(XATTRAT_OP_GET, dirfd, path, name, NULL, 0, 0); +} + +ssize_t local_removexattr_nofollow(FsContext *ctx, const char *path, + const char *name) +{ + char *dirpath =3D g_path_get_dirname(path); + char *filename =3D g_path_get_basename(path); + int dirfd; + ssize_t ret =3D -1; + + dirfd =3D local_opendir_nofollow(ctx, dirpath); + if (dirfd =3D=3D -1) { + goto out; + } + + ret =3D fremovexattrat_nofollow(dirfd, filename, name); + close_preserve_errno(dirfd); +out: + g_free(dirpath); + g_free(filename); + return ret; +} =20 static XattrOperations *get_xattr_operations(XattrOperations **h, const char *name) diff --git a/hw/9pfs/9p-xattr.h b/hw/9pfs/9p-xattr.h index 3f43f5153f3c..986cb59b67f2 100644 --- a/hw/9pfs/9p-xattr.h +++ b/hw/9pfs/9p-xattr.h @@ -15,6 +15,7 @@ #define QEMU_9P_XATTR_H =20 #include "qemu/xattr.h" +#include "9p-local.h" =20 typedef struct xattr_operations { @@ -29,6 +30,18 @@ typedef struct xattr_operations const char *path, const char *name); } XattrOperations; =20 +ssize_t fgetxattrat_nofollow(int dirfd, const char *path, const char *name, + void *value, size_t size); +int fsetxattrat_nofollow(int dirfd, const char *path, const char *name, + void *value, size_t size, int flags); + +ssize_t local_getxattr_nofollow(FsContext *ctx, const char *path, + const char *name, void *value, size_t size= ); +ssize_t local_setxattr_nofollow(FsContext *ctx, const char *path, + const char *name, void *value, size_t size, + int flags); +ssize_t local_removexattr_nofollow(FsContext *ctx, const char *path, + const char *name); =20 extern XattrOperations mapped_user_xattr; extern XattrOperations passthrough_user_xattr;