From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from smtp-42a9.mail.infomaniak.ch (smtp-42a9.mail.infomaniak.ch [84.16.66.169]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id DDB67313550 for ; Fri, 22 Aug 2025 17:08:20 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=84.16.66.169 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755882503; cv=none; b=cj2IEqNSCPpEeeOSblR/xyujr1rU8XWNMT9JECWQKZOUWnTGpvICM3S08W4QtX6WZgP6OMFiTE5yFLLVNBSDMlq2R+J1PRCqB0p3JsYB/aoVX2k6ZkTcpUWSEKfGFRrBTr5ClZNFG3scPMsQdwwLBOs9axv++Ms1E0PeZtpmjG8= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755882503; c=relaxed/simple; bh=a0bfPIZilwtzdMjRhg2FvF4FRux6GrPkvAFHgegPRWg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=j42FqhaMSiDpnccHzmKCbN7AFfGzQsaErTfisYy69CYwUiREnxxhhzwJIV6F7e+92iNj/INx2/WSrok3MxM1KbpmpsJ0+dp24Ih5wvaEEdXmzl7vGwM53Sp6lA8NKE8MhWkVtsqmwzwyt5b6IQyAG2cSkj2VCTeZNBz6CB8wXF0= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=digikod.net; spf=pass smtp.mailfrom=digikod.net; dkim=pass (1024-bit key) header.d=digikod.net header.i=@digikod.net header.b=APfe4TuR; arc=none smtp.client-ip=84.16.66.169 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=digikod.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=digikod.net Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=digikod.net header.i=@digikod.net header.b="APfe4TuR" Received: from smtp-4-0001.mail.infomaniak.ch (unknown [IPv6:2001:1600:7:10::a6c]) by smtp-4-3000.mail.infomaniak.ch (Postfix) with ESMTPS id 4c7mq14Y0Sz1165; Fri, 22 Aug 2025 19:08:13 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=digikod.net; s=20191114; t=1755882493; bh=4Pohfi350pEoPU/zQg61jutpwKFWUO6VrrmrOVVvjEM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=APfe4TuRZ8GRVIc6wtAriNhinbsnaFb80/g+PAYZK5y1ijvDyKgUrOGg8Z6sMzHBm XUzd6/DsMx4itm5EMSqx1SxeYAwh4mcvdQnzQz/BLDE6sxYHAQ7GayducB/U4S3NVL L5Vp0PIJThySseul7YYLSaDqfOgnaBC25FoZbeC0= Received: from unknown by smtp-4-0001.mail.infomaniak.ch (Postfix) with ESMTPA id 4c7mq04JRvznCZ; Fri, 22 Aug 2025 19:08:12 +0200 (CEST) From: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= To: Al Viro , Christian Brauner , Kees Cook , Paul Moore , Serge Hallyn Cc: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= , Andy Lutomirski , Arnd Bergmann , Christian Heimes , Dmitry Vyukov , Elliott Hughes , Fan Wu , Florian Weimer , Jann Horn , Jeff Xu , Jonathan Corbet , Jordan R Abrahams , Lakshmi Ramasubramanian , Luca Boccassi , Matt Bobrowski , Miklos Szeredi , Mimi Zohar , Nicolas Bouchinet , Robert Waite , Roberto Sassu , Scott Shell , Steve Dower , Steve Grubb , kernel-hardening@lists.openwall.com, linux-api@vger.kernel.org, linux-fsdevel@vger.kernel.org, linux-integrity@vger.kernel.org, linux-kernel@vger.kernel.org, linux-security-module@vger.kernel.org, Andy Lutomirski , Jeff Xu Subject: [RFC PATCH v1 2/2] selftests/exec: Add O_DENY_WRITE tests Date: Fri, 22 Aug 2025 19:08:00 +0200 Message-ID: <20250822170800.2116980-3-mic@digikod.net> In-Reply-To: <20250822170800.2116980-1-mic@digikod.net> References: <20250822170800.2116980-1-mic@digikod.net> Precedence: bulk X-Mailing-List: linux-security-module@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-Infomaniak-Routing: alpha Add 6 test suites to check O_DENY_WRITE used through open(2) and fcntl(2). Check that it fills its purpose, that it only applies to regular files, and that setting this flag several times is not an issue. The O_DENY_WRITE flag is useful in conjunction with AT_EXECVE_CHECK for systems that don't enforce a write-xor-execute policy. Extend related tests to also use them as examples. Cc: Al Viro Cc: Andy Lutomirski Cc: Christian Brauner Cc: Jeff Xu Cc: Kees Cook Cc: Paul Moore Cc: Robert Waite Cc: Serge Hallyn Signed-off-by: Mickaël Salaün Link: https://lore.kernel.org/r/20250822170800.2116980-3-mic@digikod.net --- tools/testing/selftests/exec/check-exec.c | 219 ++++++++++++++++++++++ 1 file changed, 219 insertions(+) diff --git a/tools/testing/selftests/exec/check-exec.c b/tools/testing/selftests/exec/check-exec.c index 55bce47e56b7..9db1d7b9aa97 100644 --- a/tools/testing/selftests/exec/check-exec.c +++ b/tools/testing/selftests/exec/check-exec.c @@ -30,6 +30,10 @@ #define _ASM_GENERIC_FCNTL_H #include +#ifndef O_DENY_WRITE +#define O_DENY_WRITE 040000000 +#endif + #include "../kselftest_harness.h" static int sys_execveat(int dirfd, const char *pathname, char *const argv[], @@ -319,6 +323,221 @@ TEST_F(access, non_regular_files) test_exec_fd(_metadata, self->pipefd, EACCES); } +TEST_F(access, deny_write_check_open) +{ + int fd_deny, fd_read, fd_write; + + fd_deny = open(reg_file_path, O_DENY_WRITE | O_RDONLY | O_CLOEXEC); + ASSERT_LE(0, fd_deny); + + /* Concurrent reads are always allowed. */ + fd_read = open(reg_file_path, O_RDONLY | O_CLOEXEC); + EXPECT_LE(0, fd_read); + EXPECT_EQ(0, close(fd_read)); + + /* Concurrent writes are denied. */ + fd_write = open(reg_file_path, O_WRONLY | O_CLOEXEC); + EXPECT_EQ(-1, fd_write); + EXPECT_EQ(ETXTBSY, errno); + + /* Drops O_DENY_WRITE. */ + EXPECT_EQ(0, close(fd_deny)); + + /* The restriction is now gone. */ + fd_write = open(reg_file_path, O_WRONLY | O_CLOEXEC); + EXPECT_LE(0, fd_write); + EXPECT_EQ(0, close(fd_write)); +} + +TEST_F(access, deny_write_check_open_and_fcntl) +{ + int fd_deny, fd_read, fd_write, flags; + + fd_deny = open(reg_file_path, O_DENY_WRITE | O_RDONLY | O_CLOEXEC); + ASSERT_LE(0, fd_deny); + + /* Sets O_DENY_WRITE a "second" time. */ + flags = fcntl(fd_deny, F_GETFL); + ASSERT_NE(-1, flags); + EXPECT_EQ(0, fcntl(fd_deny, F_SETFL, flags | O_DENY_WRITE)); + + /* Concurrent reads are always allowed. */ + fd_read = open(reg_file_path, O_RDONLY | O_CLOEXEC); + EXPECT_LE(0, fd_read); + EXPECT_EQ(0, close(fd_read)); + + /* Concurrent writes are denied. */ + fd_write = open(reg_file_path, O_WRONLY | O_CLOEXEC); + EXPECT_EQ(-1, fd_write); + EXPECT_EQ(ETXTBSY, errno); + + /* Drops O_DENY_WRITE. */ + EXPECT_EQ(0, close(fd_deny)); + + /* The restriction is now gone. */ + fd_write = open(reg_file_path, O_WRONLY | O_CLOEXEC); + EXPECT_LE(0, fd_write); + EXPECT_EQ(0, close(fd_write)); +} + +TEST_F(access, deny_write_check_fcntl) +{ + int fd_deny, fd_read, fd_write, flags; + + fd_deny = open(reg_file_path, O_RDONLY | O_CLOEXEC); + ASSERT_LE(0, fd_deny); + + /* Sets O_DENY_WRITE a first time. */ + flags = fcntl(fd_deny, F_GETFL); + ASSERT_NE(-1, flags); + EXPECT_EQ(0, fcntl(fd_deny, F_SETFL, flags | O_DENY_WRITE)); + + /* Sets O_DENY_WRITE a "second" time. */ + EXPECT_EQ(0, fcntl(fd_deny, F_SETFL, flags | O_DENY_WRITE)); + + /* Concurrent reads are always allowed. */ + fd_read = open(reg_file_path, O_RDONLY | O_CLOEXEC); + EXPECT_LE(0, fd_read); + EXPECT_EQ(0, close(fd_read)); + + /* Concurrent writes are denied. */ + fd_write = open(reg_file_path, O_WRONLY | O_CLOEXEC); + EXPECT_EQ(-1, fd_write); + EXPECT_EQ(ETXTBSY, errno); + + /* Drops O_DENY_WRITE. */ + EXPECT_EQ(0, fcntl(fd_deny, F_SETFL, flags & ~O_DENY_WRITE)); + + /* The restriction is now gone. */ + fd_write = open(reg_file_path, O_WRONLY | O_CLOEXEC); + EXPECT_LE(0, fd_write); + EXPECT_EQ(0, close(fd_write)); + + EXPECT_EQ(0, close(fd_deny)); +} + +static void test_deny_write_open(struct __test_metadata *_metadata, + const char *const path, int flags, + const int err_code) +{ + int fd; + + flags |= O_CLOEXEC; + + /* Do not block on pipes. */ + if (path == fifo_path) + flags |= O_NONBLOCK; + + fd = open(path, flags | O_RDONLY); + if (err_code) { + ASSERT_EQ(-1, fd) + { + TH_LOG("Successfully opened %s", path); + } + EXPECT_EQ(errno, err_code) + { + TH_LOG("Wrong error code for %s: %s", path, + strerror(errno)); + } + } else { + ASSERT_LE(0, fd) + { + TH_LOG("Failed to open %s: %s", path, strerror(errno)); + } + EXPECT_EQ(0, close(fd)); + } +} + +TEST_F(access, deny_write_type_open) +{ + test_deny_write_open(_metadata, reg_file_path, O_DENY_WRITE, 0); + test_deny_write_open(_metadata, dir_path, O_DENY_WRITE, EINVAL); + test_deny_write_open(_metadata, block_dev_path, O_DENY_WRITE, EINVAL); + test_deny_write_open(_metadata, char_dev_path, O_DENY_WRITE, EINVAL); + test_deny_write_open(_metadata, fifo_path, O_DENY_WRITE, EINVAL); +} + +static void test_deny_write_fcntl(struct __test_metadata *_metadata, + const char *const path, int setfl, + const int err_code) +{ + int fd, ret; + int getfl, flags = O_CLOEXEC; + + /* Do not block on pipes. */ + if (path == fifo_path) + flags |= O_NONBLOCK; + + fd = open(path, flags | O_RDONLY); + ASSERT_LE(0, fd) + { + TH_LOG("Failed to open %s: %s", path, strerror(errno)); + } + getfl = fcntl(fd, F_GETFL); + ASSERT_NE(-1, getfl); + ret = fcntl(fd, F_SETFL, getfl | setfl); + if (err_code) { + ASSERT_EQ(-1, ret) + { + TH_LOG("Successfully updated flags for %s", path); + } + EXPECT_EQ(errno, err_code) + { + TH_LOG("Wrong error code for %s: %s", path, + strerror(errno)); + } + } else { + ASSERT_LE(0, ret) + { + TH_LOG("Failed to update flags for %s: %s", path, + strerror(errno)); + } + EXPECT_EQ(0, close(fd)); + } +} + +TEST_F(access, deny_write_type_fcntl) +{ + int flags; + + test_deny_write_fcntl(_metadata, reg_file_path, O_DENY_WRITE, 0); + test_deny_write_fcntl(_metadata, dir_path, O_DENY_WRITE, EINVAL); + test_deny_write_fcntl(_metadata, block_dev_path, O_DENY_WRITE, EINVAL); + test_deny_write_fcntl(_metadata, char_dev_path, O_DENY_WRITE, EINVAL); + test_deny_write_fcntl(_metadata, fifo_path, O_DENY_WRITE, EINVAL); + + flags = fcntl(self->socket_fds[0], F_GETFL); + ASSERT_NE(-1, flags); + EXPECT_EQ(-1, + fcntl(self->socket_fds[0], F_SETFL, flags | O_DENY_WRITE)); + EXPECT_EQ(EINVAL, errno); + + flags = fcntl(self->pipefd, F_GETFL); + ASSERT_NE(-1, flags); + EXPECT_EQ(-1, fcntl(self->pipefd, F_SETFL, flags | O_DENY_WRITE)); + EXPECT_EQ(EINVAL, errno); +} + +TEST_F(access, allow_write_type_fcntl) +{ + int flags; + + test_deny_write_fcntl(_metadata, reg_file_path, 0, 0); + test_deny_write_fcntl(_metadata, dir_path, 0, 0); + test_deny_write_fcntl(_metadata, block_dev_path, 0, 0); + test_deny_write_fcntl(_metadata, char_dev_path, 0, 0); + test_deny_write_fcntl(_metadata, fifo_path, 0, 0); + + flags = fcntl(self->socket_fds[0], F_GETFL); + ASSERT_NE(-1, flags); + EXPECT_EQ(0, + fcntl(self->socket_fds[0], F_SETFL, flags & ~O_DENY_WRITE)); + + flags = fcntl(self->pipefd, F_GETFL); + ASSERT_NE(-1, flags); + EXPECT_EQ(0, fcntl(self->pipefd, F_SETFL, flags & ~O_DENY_WRITE)); +} + /* clang-format off */ FIXTURE(secbits) {}; /* clang-format on */ -- 2.50.1