From patchwork Tue Mar 21 20:15:32 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ackerley Tng X-Patchwork-Id: 1759613 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=nongnu.org (client-ip=209.51.188.17; helo=lists.gnu.org; envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org; receiver=) Authentication-Results: legolas.ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=google.com header.i=@google.com header.a=rsa-sha256 header.s=20210112 header.b=nkDLVeFO; dkim-atps=neutral Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-ECDSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4Ph2tm10mYz247F for ; Wed, 22 Mar 2023 07:16:32 +1100 (AEDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1peiOw-0004Ih-LL; Tue, 21 Mar 2023 16:16:13 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from <3cBAaZAsKCiE79HBOIBVQKDDLLDIB.9LJNBJR-ABSBIKLKDKR.LOD@flex--ackerleytng.bounces.google.com>) id 1peiOa-00047F-Lh for qemu-devel@nongnu.org; Tue, 21 Mar 2023 16:15:56 -0400 Received: from mail-pf1-x44a.google.com ([2607:f8b0:4864:20::44a]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from <3cBAaZAsKCiE79HBOIBVQKDDLLDIB.9LJNBJR-ABSBIKLKDKR.LOD@flex--ackerleytng.bounces.google.com>) id 1peiOY-00023Y-JR for qemu-devel@nongnu.org; Tue, 21 Mar 2023 16:15:48 -0400 Received: by mail-pf1-x44a.google.com with SMTP id i26-20020aa796fa000000b006261da7aeceso6519415pfq.5 for ; Tue, 21 Mar 2023 13:15:45 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20210112; t=1679429744; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=SASYwsYhAQSqNIE6HUln5R808O00UDaM+ugmJxC1i6Y=; b=nkDLVeFOEtNHIk2/9xYa/CQ5Y+2ViSmqdwKS37RKpN9fN1MXmDMz1OU9puFW4g8bxR vii0vfViksMKRckdTjgGuQRIZo78lhmcmIzghCZBBHxvW/ndiQpVU4djvbvM+g/7wUMs +h4BMeKpfniUSkYQ2+iXYK1OzQ6Zixd18XxuCFovZYEJjKkL+JZVoFx8wgMxsnNIhhrW cwAnxft2O57Rn97HBOHm6iE115uNcFbAUa0j/WlnnPQnSn42zEaUa3fkNCQ6d75oG0jY i+hqkZSaarDOufSFczQvwQKpFfhRDRSFIn/93xiAf3T7ZrsK4NycMXJEfB2VVpheLbup 9TAw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; t=1679429744; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=SASYwsYhAQSqNIE6HUln5R808O00UDaM+ugmJxC1i6Y=; b=S8Pye4JBft6qt6TxKe5I8f1J6oDCQN9jDTyU061QWvnzJzy20eeD8yd29zmdDT/xBn ALBlUow/HQKcMaYRqBXoY9U47IjyAhNGGyPi3CmGjbXsqxE577bdKFRNTgmCnmUMToDt ONbAJrGwf7Ld1kGnHxeI48yqFzMOjiqJsYTEAIOh2JcXDilMMpR26n9Xo63pB8x05+Fn Cw7Q9Z/NM+7kNVxd06t6cJaO55OsX8KvdawYqTVevsTIfMLHlvzFTBu8dBKNR0RIvP+T MQPW8Kj1/CYgL9KUbvMVN0bpPQYfp2v3pT0l+/RHYVKoESkIbetErbU2SK/dBXisripq sX/A== X-Gm-Message-State: AO0yUKUb2P/t2z0OSirNzXwHYkZCb7lzH7TT/RnBlSzHaucGv5q6vVaG nouRkNtYRQ5EpS/s+1ux/KmN1HR6cQtj7N7eXQ== X-Google-Smtp-Source: AK7set/HvJzxMftd1uCLemfdr7EZh/avv/uPbf4VraKazWicqnhqmpj6db9AHljzqSRdUL4ivIO6IsbGNBTLA3WbfA== X-Received: from ackerleytng-cloudtop.c.googlers.com ([fda3:e722:ac3:cc00:7f:e700:c0a8:1f5f]) (user=ackerleytng job=sendgmr) by 2002:a17:902:b612:b0:1a0:51f7:7f5a with SMTP id b18-20020a170902b61200b001a051f77f5amr137446pls.13.1679429744410; Tue, 21 Mar 2023 13:15:44 -0700 (PDT) Date: Tue, 21 Mar 2023 20:15:32 +0000 In-Reply-To: Mime-Version: 1.0 References: X-Mailer: git-send-email 2.40.0.rc2.332.ga46443480c-goog Message-ID: <6e800e069c7fc400841b75ea49d1227bd101c1cf.1679428901.git.ackerleytng@google.com> Subject: [RFC PATCH v2 1/2] mm: restrictedmem: Allow userspace to specify mount for memfd_restricted From: Ackerley Tng To: kvm@vger.kernel.org, linux-api@vger.kernel.org, linux-arch@vger.kernel.org, linux-doc@vger.kernel.org, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, linux-mm@kvack.org, qemu-devel@nongnu.org Cc: aarcange@redhat.com, ak@linux.intel.com, akpm@linux-foundation.org, arnd@arndb.de, bfields@fieldses.org, bp@alien8.de, chao.p.peng@linux.intel.com, corbet@lwn.net, dave.hansen@intel.com, david@redhat.com, ddutile@redhat.com, dhildenb@redhat.com, hpa@zytor.com, hughd@google.com, jlayton@kernel.org, jmattson@google.com, joro@8bytes.org, jun.nakajima@intel.com, kirill.shutemov@linux.intel.com, linmiaohe@huawei.com, luto@kernel.org, mail@maciej.szmigiero.name, mhocko@suse.com, michael.roth@amd.com, mingo@redhat.com, naoya.horiguchi@nec.com, pbonzini@redhat.com, qperret@google.com, rppt@kernel.org, seanjc@google.com, shuah@kernel.org, steven.price@arm.com, tabba@google.com, tglx@linutronix.de, vannapurve@google.com, vbabka@suse.cz, vkuznets@redhat.com, wanpengli@tencent.com, wei.w.wang@intel.com, x86@kernel.org, yu.c.zhang@linux.intel.com, Ackerley Tng Received-SPF: pass client-ip=2607:f8b0:4864:20::44a; envelope-from=3cBAaZAsKCiE79HBOIBVQKDDLLDIB.9LJNBJR-ABSBIKLKDKR.LOD@flex--ackerleytng.bounces.google.com; helo=mail-pf1-x44a.google.com X-Spam_score_int: -95 X-Spam_score: -9.6 X-Spam_bar: --------- X-Spam_report: (-9.6 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_MED=-0.001, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001, USER_IN_DEF_DKIM_WL=-7.5 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 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 By default, the backing shmem file for a restrictedmem fd is created on shmem's kernel space mount. With this patch, an optional tmpfs mount can be specified via an fd, which will be used as the mountpoint for backing the shmem file associated with a restrictedmem fd. This change is modeled after how sys_open() can create an unnamed temporary file in a given directory with O_TMPFILE. This will help restrictedmem fds inherit the properties of the provided tmpfs mounts, for example, hugepage allocation hints, NUMA binding hints, etc. Signed-off-by: Ackerley Tng --- include/linux/syscalls.h | 2 +- include/uapi/linux/restrictedmem.h | 8 ++++ mm/restrictedmem.c | 63 +++++++++++++++++++++++++++--- 3 files changed, 66 insertions(+), 7 deletions(-) create mode 100644 include/uapi/linux/restrictedmem.h diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h index f9e9e0c820c5..a23c4c385cd3 100644 --- a/include/linux/syscalls.h +++ b/include/linux/syscalls.h @@ -1056,7 +1056,7 @@ asmlinkage long sys_memfd_secret(unsigned int flags); asmlinkage long sys_set_mempolicy_home_node(unsigned long start, unsigned long len, unsigned long home_node, unsigned long flags); -asmlinkage long sys_memfd_restricted(unsigned int flags); +asmlinkage long sys_memfd_restricted(unsigned int flags, int mount_fd); /* * Architecture-specific system calls diff --git a/include/uapi/linux/restrictedmem.h b/include/uapi/linux/restrictedmem.h new file mode 100644 index 000000000000..9f108dd1ac4c --- /dev/null +++ b/include/uapi/linux/restrictedmem.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _UAPI_LINUX_RESTRICTEDMEM_H +#define _UAPI_LINUX_RESTRICTEDMEM_H + +/* flags for memfd_restricted */ +#define RMFD_TMPFILE 0x0001U + +#endif /* _UAPI_LINUX_RESTRICTEDMEM_H */ diff --git a/mm/restrictedmem.c b/mm/restrictedmem.c index c5d869d8c2d8..4d83b949d84e 100644 --- a/mm/restrictedmem.c +++ b/mm/restrictedmem.c @@ -1,11 +1,12 @@ // SPDX-License-Identifier: GPL-2.0 -#include "linux/sbitmap.h" +#include #include #include #include #include #include #include +#include #include struct restrictedmem { @@ -189,19 +190,20 @@ static struct file *restrictedmem_file_create(struct file *memfd) return file; } -SYSCALL_DEFINE1(memfd_restricted, unsigned int, flags) +static int restrictedmem_create(struct vfsmount *mount) { struct file *file, *restricted_file; int fd, err; - if (flags) - return -EINVAL; - fd = get_unused_fd_flags(0); if (fd < 0) return fd; - file = shmem_file_setup("memfd:restrictedmem", 0, VM_NORESERVE); + if (mount) + file = shmem_file_setup_with_mnt(mount, "memfd:restrictedmem", 0, VM_NORESERVE); + else + file = shmem_file_setup("memfd:restrictedmem", 0, VM_NORESERVE); + if (IS_ERR(file)) { err = PTR_ERR(file); goto err_fd; @@ -223,6 +225,55 @@ SYSCALL_DEFINE1(memfd_restricted, unsigned int, flags) return err; } +static bool is_shmem_mount(struct vfsmount *mnt) +{ + return mnt && mnt->mnt_sb && mnt->mnt_sb->s_magic == TMPFS_MAGIC; +} + +static int restrictedmem_create_from_file(int mount_fd) +{ + int ret; + struct fd f; + struct vfsmount *mnt; + + f = fdget_raw(mount_fd); + if (!f.file) + return -EBADF; + + mnt = f.file->f_path.mnt; + if (!is_shmem_mount(mnt)) { + ret = -EINVAL; + goto out; + } + + ret = mnt_want_write(mnt); + if (unlikely(ret)) + goto out; + + ret = restrictedmem_create(mnt); + + mnt_drop_write(mnt); +out: + fdput(f); + + return ret; +} + +SYSCALL_DEFINE2(memfd_restricted, unsigned int, flags, int, mount_fd) +{ + if (flags & ~RMFD_TMPFILE) + return -EINVAL; + + if (flags == RMFD_TMPFILE) { + if (mount_fd < 0) + return -EINVAL; + + return restrictedmem_create_from_file(mount_fd); + } else { + return restrictedmem_create(NULL); + } +} + int restrictedmem_bind(struct file *file, pgoff_t start, pgoff_t end, struct restrictedmem_notifier *notifier, bool exclusive) { From patchwork Tue Mar 21 20:15:33 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ackerley Tng X-Patchwork-Id: 1759615 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=nongnu.org (client-ip=209.51.188.17; helo=lists.gnu.org; envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org; receiver=) Authentication-Results: legolas.ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=google.com header.i=@google.com header.a=rsa-sha256 header.s=20210112 header.b=gOamuZL0; dkim-atps=neutral Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-ECDSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4Ph2ts1bhFz247F for ; Wed, 22 Mar 2023 07:16:37 +1100 (AEDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1peiOz-0004Ii-TD; Tue, 21 Mar 2023 16:16:16 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from <3chAaZAsKCiM9BJDQKDXSMFFNNFKD.BNLPDLT-CDUDKMNMFMT.NQF@flex--ackerleytng.bounces.google.com>) id 1peiOc-00047x-Bk for qemu-devel@nongnu.org; Tue, 21 Mar 2023 16:15:57 -0400 Received: from mail-pl1-x64a.google.com ([2607:f8b0:4864:20::64a]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from <3chAaZAsKCiM9BJDQKDXSMFFNNFKD.BNLPDLT-CDUDKMNMFMT.NQF@flex--ackerleytng.bounces.google.com>) id 1peiOZ-00024w-SL for qemu-devel@nongnu.org; Tue, 21 Mar 2023 16:15:50 -0400 Received: by mail-pl1-x64a.google.com with SMTP id q9-20020a170902dac900b001a18ceff5ebso9367477plx.4 for ; Tue, 21 Mar 2023 13:15:46 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20210112; t=1679429746; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=WUxn3KVcBxbFXkgOhZJm8jHzCo14A+URwIfQXLEPszQ=; b=gOamuZL09TvaiKBWLNigfj5XfPQ9ZV+G8B5xxWIHP7ReVAgFwkb2mMOqoMmAQj4JA5 a71uvwvqvuhm9Mla+M6MKfVBfAeZv8j+Ais4qiGHYQbdzN04WOFDIvW+KHiX6/6UcG3g krfkQwX/3y0i/lIiZnvQ0cDeDcI7B671x+yj/K+Slq7czkPMQa4ZsYo4hXCLHwgEBbR6 wcEzyW5vDv1w52aGipOfZRkfsbrmcbzAncEZk/uv+Uo6h534K0mNscTMHh24KGiLks3G i+rXSGXGfinc7T1/3sxRE9aQe0lPV9r2mPkO1dm8fdQ094x0CRIh1SLftIFzpGONkFQy Gw5A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; t=1679429746; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=WUxn3KVcBxbFXkgOhZJm8jHzCo14A+URwIfQXLEPszQ=; b=fCjRs0G+zxuJx40XpBWlaTFppDd6vdjKyO7oTvrY9rIu+Qh5LupgGz2mFnIdF9cF/4 tg3WjUaKgh9RF+ZOPTD9ZeWh31MW8Q8OHLGuq6YivjZ43LtrbAokOdSGBCIZB7mbpTEE XW5GNsrsJHa+KMBVVCM5zqI+GHxtnr/7pPJKMJARcPjneM0BKbbyvFDcIhcWX6m+MiO/ a37VZSpC6NguEH6qB8DK/SLVa+bebEsJd/XpBBb/9Ygm0JGpE5vK4pLR6s+tPSYe+yGF 6gUEFl9r4kOyJ7Vac3HdgSd7pXOjL//OXftFPH7OGqrCk13uQUeEGHG091WigJUQEjOy WuYA== X-Gm-Message-State: AO0yUKV7CQNdu0ZBI8SZ9+A0hB4gA5hxFKEYU9H+Yw0M/o2bBWHcIANg TLPvbHGbZpviTbk7rG/V11q4VtKnGR5kNd2lDg== X-Google-Smtp-Source: AK7set+dSS5L5IQsR2QM90zFjxvQz3FiYFZP+Sm9POtIwngHMs0+tu5Z0jbahBkmyz+8xgu0gtCE94fpBCegZt4AyQ== X-Received: from ackerleytng-cloudtop.c.googlers.com ([fda3:e722:ac3:cc00:7f:e700:c0a8:1f5f]) (user=ackerleytng job=sendgmr) by 2002:a05:6a00:99d:b0:628:fc:9049 with SMTP id u29-20020a056a00099d00b0062800fc9049mr654427pfg.4.1679429746071; Tue, 21 Mar 2023 13:15:46 -0700 (PDT) Date: Tue, 21 Mar 2023 20:15:33 +0000 In-Reply-To: Mime-Version: 1.0 References: X-Mailer: git-send-email 2.40.0.rc2.332.ga46443480c-goog Message-ID: <4db33a8976193f3eff80dbd4515335c36aeeb416.1679428901.git.ackerleytng@google.com> Subject: [RFC PATCH v2 2/2] selftests: restrictedmem: Check hugepage-ness of shmem file backing restrictedmem fd From: Ackerley Tng To: kvm@vger.kernel.org, linux-api@vger.kernel.org, linux-arch@vger.kernel.org, linux-doc@vger.kernel.org, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, linux-mm@kvack.org, qemu-devel@nongnu.org Cc: aarcange@redhat.com, ak@linux.intel.com, akpm@linux-foundation.org, arnd@arndb.de, bfields@fieldses.org, bp@alien8.de, chao.p.peng@linux.intel.com, corbet@lwn.net, dave.hansen@intel.com, david@redhat.com, ddutile@redhat.com, dhildenb@redhat.com, hpa@zytor.com, hughd@google.com, jlayton@kernel.org, jmattson@google.com, joro@8bytes.org, jun.nakajima@intel.com, kirill.shutemov@linux.intel.com, linmiaohe@huawei.com, luto@kernel.org, mail@maciej.szmigiero.name, mhocko@suse.com, michael.roth@amd.com, mingo@redhat.com, naoya.horiguchi@nec.com, pbonzini@redhat.com, qperret@google.com, rppt@kernel.org, seanjc@google.com, shuah@kernel.org, steven.price@arm.com, tabba@google.com, tglx@linutronix.de, vannapurve@google.com, vbabka@suse.cz, vkuznets@redhat.com, wanpengli@tencent.com, wei.w.wang@intel.com, x86@kernel.org, yu.c.zhang@linux.intel.com, Ackerley Tng Received-SPF: pass client-ip=2607:f8b0:4864:20::64a; envelope-from=3chAaZAsKCiM9BJDQKDXSMFFNNFKD.BNLPDLT-CDUDKMNMFMT.NQF@flex--ackerleytng.bounces.google.com; helo=mail-pl1-x64a.google.com X-Spam_score_int: -95 X-Spam_score: -9.6 X-Spam_bar: --------- X-Spam_report: (-9.6 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_MED=-0.001, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001, USER_IN_DEF_DKIM_WL=-7.5 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 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 For memfd_restricted() calls without a userspace mount, the backing file should be the shmem mount in the kernel, and the size of backing pages should be as defined by system-wide shmem configuration. If a userspace mount is provided, the size of backing pages should be as defined in the mount. Signed-off-by: Ackerley Tng --- tools/testing/selftests/Makefile | 1 + .../selftests/restrictedmem/.gitignore | 3 + .../testing/selftests/restrictedmem/Makefile | 15 + .../testing/selftests/restrictedmem/common.c | 9 + .../testing/selftests/restrictedmem/common.h | 8 + .../restrictedmem_hugepage_test.c | 459 ++++++++++++++++++ 6 files changed, 495 insertions(+) create mode 100644 tools/testing/selftests/restrictedmem/.gitignore create mode 100644 tools/testing/selftests/restrictedmem/Makefile create mode 100644 tools/testing/selftests/restrictedmem/common.c create mode 100644 tools/testing/selftests/restrictedmem/common.h create mode 100644 tools/testing/selftests/restrictedmem/restrictedmem_hugepage_test.c diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index f07aef7c592c..44078eeefb79 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -60,6 +60,7 @@ TARGETS += pstore TARGETS += ptrace TARGETS += openat2 TARGETS += resctrl +TARGETS += restrictedmem TARGETS += rlimits TARGETS += rseq TARGETS += rtc diff --git a/tools/testing/selftests/restrictedmem/.gitignore b/tools/testing/selftests/restrictedmem/.gitignore new file mode 100644 index 000000000000..2581bcc8ff29 --- /dev/null +++ b/tools/testing/selftests/restrictedmem/.gitignore @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only + +restrictedmem_hugepage_test diff --git a/tools/testing/selftests/restrictedmem/Makefile b/tools/testing/selftests/restrictedmem/Makefile new file mode 100644 index 000000000000..8e5378d20226 --- /dev/null +++ b/tools/testing/selftests/restrictedmem/Makefile @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0 + +CFLAGS = $(KHDR_INCLUDES) +CFLAGS += -Wall -Wstrict-prototypes -Wuninitialized -std=gnu99 + +TEST_GEN_PROGS += restrictedmem_hugepage_test + +include ../lib.mk + +EXTRA_CLEAN = $(OUTPUT)/common.o + +$(OUTPUT)/common.o: common.c + $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c -ffreestanding $< -o $@ + +$(TEST_GEN_PROGS): $(OUTPUT)/common.o diff --git a/tools/testing/selftests/restrictedmem/common.c b/tools/testing/selftests/restrictedmem/common.c new file mode 100644 index 000000000000..03dac843404f --- /dev/null +++ b/tools/testing/selftests/restrictedmem/common.c @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include + +int memfd_restricted(unsigned int flags, int mount_fd) +{ + return syscall(__NR_memfd_restricted, flags, mount_fd); +} diff --git a/tools/testing/selftests/restrictedmem/common.h b/tools/testing/selftests/restrictedmem/common.h new file mode 100644 index 000000000000..06284ed86baf --- /dev/null +++ b/tools/testing/selftests/restrictedmem/common.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef SELFTESTS_RESTRICTEDMEM_COMMON_H +#define SELFTESTS_RESTRICTEDMEM_COMMON_H + +int memfd_restricted(unsigned int flags, int mount_fd); + +#endif // SELFTESTS_RESTRICTEDMEM_COMMON_H diff --git a/tools/testing/selftests/restrictedmem/restrictedmem_hugepage_test.c b/tools/testing/selftests/restrictedmem/restrictedmem_hugepage_test.c new file mode 100644 index 000000000000..ae37148342fe --- /dev/null +++ b/tools/testing/selftests/restrictedmem/restrictedmem_hugepage_test.c @@ -0,0 +1,459 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#define _GNU_SOURCE /* for O_PATH */ +#define _POSIX_C_SOURCE /* for PATH_MAX */ +#include +#include +#include +#include +#include +#include +#include + +#include "linux/restrictedmem.h" + +#include "common.h" +#include "../kselftest_harness.h" + +/* + * Expect policy to be one of always, within_size, advise, never, + * deny, force + */ +#define POLICY_BUF_SIZE 12 + +static int get_hpage_pmd_size(void) +{ + FILE *fp; + char buf[100]; + char *ret; + int size; + + fp = fopen("/sys/kernel/mm/transparent_hugepage/hpage_pmd_size", "r"); + if (!fp) + return -1; + + ret = fgets(buf, 100, fp); + if (ret != buf) { + size = -1; + goto out; + } + + if (sscanf(buf, "%d\n", &size) != 1) + size = -1; + +out: + fclose(fp); + + return size; +} + +static bool is_valid_shmem_thp_policy(char *policy) +{ + if (strcmp(policy, "always") == 0) + return true; + if (strcmp(policy, "within_size") == 0) + return true; + if (strcmp(policy, "advise") == 0) + return true; + if (strcmp(policy, "never") == 0) + return true; + if (strcmp(policy, "deny") == 0) + return true; + if (strcmp(policy, "force") == 0) + return true; + + return false; +} + +static int get_shmem_thp_policy(char *policy) +{ + FILE *fp; + char buf[100]; + char *left = NULL; + char *right = NULL; + int ret = -1; + + fp = fopen("/sys/kernel/mm/transparent_hugepage/shmem_enabled", "r"); + if (!fp) + return -1; + + if (fgets(buf, 100, fp) != buf) + goto out; + + /* + * Expect shmem_enabled to be of format like "always within_size advise + * [never] deny force" + */ + left = memchr(buf, '[', 100); + if (!left) + goto out; + + right = memchr(buf, ']', 100); + if (!right) + goto out; + + memcpy(policy, left + 1, right - left - 1); + + ret = !is_valid_shmem_thp_policy(policy); + +out: + fclose(fp); + return ret; +} + +static int write_string_to_file(const char *path, const char *string) +{ + FILE *fp; + size_t len = strlen(string); + int ret = -1; + + fp = fopen(path, "w"); + if (!fp) + return ret; + + if (fwrite(string, 1, len, fp) != len) + goto out; + + ret = 0; + +out: + fclose(fp); + return ret; +} + +static int set_shmem_thp_policy(char *policy) +{ + int ret = -1; + /* +1 for newline */ + char to_write[POLICY_BUF_SIZE + 1] = { 0 }; + + if (!is_valid_shmem_thp_policy(policy)) + return ret; + + ret = snprintf(to_write, POLICY_BUF_SIZE + 1, "%s\n", policy); + if (ret != strlen(policy) + 1) + return -1; + + ret = write_string_to_file( + "/sys/kernel/mm/transparent_hugepage/shmem_enabled", to_write); + + return ret; +} + +FIXTURE(reset_shmem_enabled) +{ + char shmem_enabled[POLICY_BUF_SIZE]; +}; + +FIXTURE_SETUP(reset_shmem_enabled) +{ + memset(self->shmem_enabled, 0, POLICY_BUF_SIZE); + ASSERT_EQ(0, get_shmem_thp_policy(self->shmem_enabled)); +} + +FIXTURE_TEARDOWN(reset_shmem_enabled) +{ + ASSERT_EQ(0, set_shmem_thp_policy(self->shmem_enabled)); +} + +TEST_F(reset_shmem_enabled, restrictedmem_fstat_shmem_enabled_never) +{ + int fd = -1; + struct stat stat; + + ASSERT_EQ(0, set_shmem_thp_policy("never")); + + fd = memfd_restricted(0, -1); + ASSERT_NE(-1, fd); + + ASSERT_EQ(0, fstat(fd, &stat)); + + /* + * st_blksize is set based on the superblock's s_blocksize_bits. For + * shmem, this is set to PAGE_SHIFT + */ + ASSERT_EQ(stat.st_blksize, getpagesize()); + + close(fd); +} + +TEST_F(reset_shmem_enabled, restrictedmem_fstat_shmem_enabled_always) +{ + int fd = -1; + struct stat stat; + + ASSERT_EQ(0, set_shmem_thp_policy("always")); + + fd = memfd_restricted(0, -1); + ASSERT_NE(-1, fd); + + ASSERT_EQ(0, fstat(fd, &stat)); + + ASSERT_EQ(stat.st_blksize, get_hpage_pmd_size()); + + close(fd); +} + +TEST(restrictedmem_tmpfile_invalid_fd) +{ + int fd = memfd_restricted(RMFD_TMPFILE, -2); + + ASSERT_EQ(-1, fd); + ASSERT_EQ(EINVAL, errno); +} + +TEST(restrictedmem_tmpfile_fd_not_a_mount) +{ + int fd = memfd_restricted(RMFD_TMPFILE, STDOUT_FILENO); + + ASSERT_EQ(-1, fd); + ASSERT_EQ(EINVAL, errno); +} + +TEST(restrictedmem_tmpfile_not_tmpfs_mount) +{ + int fd = -1; + int mfd = -1; + + mfd = open("/proc", O_PATH); + ASSERT_NE(-1, mfd); + + fd = memfd_restricted(RMFD_TMPFILE, mfd); + + ASSERT_EQ(-1, fd); + ASSERT_EQ(EINVAL, errno); +} + +FIXTURE(tmpfs_hugepage_sfd) +{ + int sfd; +}; + +FIXTURE_SETUP(tmpfs_hugepage_sfd) +{ + self->sfd = fsopen("tmpfs", 0); + ASSERT_NE(-1, self->sfd); +} + +FIXTURE_TEARDOWN(tmpfs_hugepage_sfd) +{ + close(self->sfd); +} + +TEST_F(tmpfs_hugepage_sfd, restrictedmem_fstat_tmpfs_huge_always) +{ + int ret = -1; + int fd = -1; + int mfd = -1; + struct stat stat; + + fsconfig(self->sfd, FSCONFIG_SET_STRING, "huge", "always", 0); + fsconfig(self->sfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0); + + mfd = fsmount(self->sfd, 0, 0); + ASSERT_NE(-1, mfd); + + fd = memfd_restricted(RMFD_TMPFILE, mfd); + ASSERT_NE(-1, fd); + + /* User can close reference to mount */ + ret = close(mfd); + ASSERT_EQ(0, ret); + + ret = fstat(fd, &stat); + ASSERT_EQ(0, ret); + ASSERT_EQ(stat.st_blksize, get_hpage_pmd_size()); + + close(fd); +} + +TEST_F(tmpfs_hugepage_sfd, restrictedmem_fstat_tmpfs_huge_never) +{ + int ret = -1; + int fd = -1; + int mfd = -1; + struct stat stat; + + fsconfig(self->sfd, FSCONFIG_SET_STRING, "huge", "never", 0); + fsconfig(self->sfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0); + + mfd = fsmount(self->sfd, 0, 0); + ASSERT_NE(-1, mfd); + + fd = memfd_restricted(RMFD_TMPFILE, mfd); + ASSERT_NE(-1, fd); + + /* User can close reference to mount */ + ret = close(mfd); + ASSERT_EQ(0, ret); + + ret = fstat(fd, &stat); + ASSERT_EQ(0, ret); + ASSERT_EQ(stat.st_blksize, getpagesize()); + + close(fd); +} + +static bool directory_exists(const char *path) +{ + struct stat sb; + + return stat(path, &sb) == 0 && S_ISDIR(sb.st_mode); +} + +FIXTURE(tmpfs_hugepage_mount_path) +{ + char *mount_path; +}; + +FIXTURE_SETUP(tmpfs_hugepage_mount_path) +{ + int ret = -1; + + /* /tmp is an FHS-mandated world-writable directory */ + self->mount_path = "/tmp/restrictedmem-selftest-mnt"; + + if (!directory_exists(self->mount_path)) { + ret = mkdir(self->mount_path, 0777); + ASSERT_EQ(0, ret); + } +} + +FIXTURE_TEARDOWN(tmpfs_hugepage_mount_path) +{ + int ret = -1; + + if (!directory_exists(self->mount_path)) + return; + + ret = umount2(self->mount_path, MNT_FORCE); + EXPECT_EQ(0, ret); + if (ret == -1 && errno == EINVAL) + fprintf(stderr, "%s was not mounted\n", self->mount_path); + + ret = rmdir(self->mount_path); + EXPECT_EQ(0, ret); + if (ret == -1) + fprintf(stderr, "rmdir(%s) failed\n", self->mount_path); +} + +/* + * When the restrictedmem's fd is open, a user should not be able to unmount or + * remove the mounted directory + */ +TEST_F(tmpfs_hugepage_mount_path, restrictedmem_umount_rmdir_while_file_open) +{ + int ret = -1; + int fd = -1; + int mfd = -1; + struct stat stat; + + ret = mount("name", self->mount_path, "tmpfs", 0, "huge=always"); + ASSERT_EQ(0, ret); + + mfd = open(self->mount_path, O_PATH); + ASSERT_NE(-1, mfd); + + fd = memfd_restricted(RMFD_TMPFILE, mfd); + ASSERT_NE(-1, fd); + + /* We don't need this reference to the mount anymore */ + ret = close(mfd); + ASSERT_EQ(0, ret); + + /* restrictedmem's fd should still be usable */ + ret = fstat(fd, &stat); + ASSERT_EQ(0, ret); + ASSERT_EQ(stat.st_blksize, get_hpage_pmd_size()); + + /* User should not be able to unmount directory */ + ret = umount2(self->mount_path, MNT_FORCE); + ASSERT_EQ(-1, ret); + ASSERT_EQ(EBUSY, errno); + + ret = rmdir(self->mount_path); + ASSERT_EQ(-1, ret); + ASSERT_EQ(EBUSY, errno); + + close(fd); +} + +/* The fd of a file on the mount can be provided as mount_fd */ +TEST_F(tmpfs_hugepage_mount_path, restrictedmem_provide_fd_of_file) +{ + int ret = -1; + int fd = -1; + int ffd = -1; + char tmp_file_path[PATH_MAX] = { 0 }; + struct stat stat; + + ret = mount("name", self->mount_path, "tmpfs", 0, "huge=always"); + ASSERT_EQ(0, ret); + + snprintf(tmp_file_path, PATH_MAX, "%s/tmp-file", self->mount_path); + ret = write_string_to_file(tmp_file_path, "filler\n"); + ASSERT_EQ(0, ret); + + ffd = open(tmp_file_path, O_RDWR); + ASSERT_NE(-1, ffd); + + fd = memfd_restricted(RMFD_TMPFILE, ffd); + ASSERT_NE(-1, fd); + + /* We don't need this reference anymore */ + ret = close(ffd); + ASSERT_EQ(0, ret); + + ret = fstat(fd, &stat); + ASSERT_EQ(0, ret); + ASSERT_EQ(stat.st_blksize, get_hpage_pmd_size()); + + close(fd); + remove(tmp_file_path); +} + +/* + * The fd of any file on the mount (including subdirectories) can be provided as + * mount_fd + */ +TEST_F(tmpfs_hugepage_mount_path, restrictedmem_provide_fd_of_file_in_subdir) +{ + int ret = -1; + int fd = -1; + int ffd = -1; + char tmp_dir_path[PATH_MAX] = { 0 }; + char tmp_file_path[PATH_MAX] = { 0 }; + struct stat stat; + + ret = mount("name", self->mount_path, "tmpfs", 0, "huge=always"); + ASSERT_EQ(0, ret); + + snprintf(tmp_dir_path, PATH_MAX, "%s/tmp-subdir", self->mount_path); + ret = mkdir(tmp_dir_path, 0777); + ASSERT_EQ(0, ret); + + snprintf(tmp_file_path, PATH_MAX, "%s/tmp-subdir/tmp-file", + self->mount_path); + ret = write_string_to_file(tmp_file_path, "filler\n"); + ASSERT_EQ(0, ret); + + ffd = open(tmp_file_path, O_RDWR); + ASSERT_NE(-1, ffd); + + fd = memfd_restricted(RMFD_TMPFILE, ffd); + ASSERT_NE(-1, fd); + + /* We don't need this reference anymore */ + ret = close(ffd); + ASSERT_EQ(0, ret); + + ret = fstat(fd, &stat); + ASSERT_EQ(0, ret); + ASSERT_EQ(stat.st_blksize, get_hpage_pmd_size()); + + close(fd); + remove(tmp_file_path); + rmdir(tmp_dir_path); +} + +TEST_HARNESS_MAIN