[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20250822170800.2116980-3-mic@digikod.net>
Date: Fri, 22 Aug 2025 19:08:00 +0200
From: Mickaël Salaün <mic@...ikod.net>
To: Al Viro <viro@...iv.linux.org.uk>,
Christian Brauner <brauner@...nel.org>,
Kees Cook <keescook@...omium.org>,
Paul Moore <paul@...l-moore.com>,
Serge Hallyn <serge@...lyn.com>
Cc: Mickaël Salaün <mic@...ikod.net>,
Andy Lutomirski <luto@...nel.org>,
Arnd Bergmann <arnd@...db.de>,
Christian Heimes <christian@...hon.org>,
Dmitry Vyukov <dvyukov@...gle.com>,
Elliott Hughes <enh@...gle.com>,
Fan Wu <wufan@...ux.microsoft.com>,
Florian Weimer <fweimer@...hat.com>,
Jann Horn <jannh@...gle.com>,
Jeff Xu <jeffxu@...gle.com>,
Jonathan Corbet <corbet@....net>,
Jordan R Abrahams <ajordanr@...gle.com>,
Lakshmi Ramasubramanian <nramas@...ux.microsoft.com>,
Luca Boccassi <bluca@...ian.org>,
Matt Bobrowski <mattbobrowski@...gle.com>,
Miklos Szeredi <mszeredi@...hat.com>,
Mimi Zohar <zohar@...ux.ibm.com>,
Nicolas Bouchinet <nicolas.bouchinet@....cyber.gouv.fr>,
Robert Waite <rowait@...rosoft.com>,
Roberto Sassu <roberto.sassu@...wei.com>,
Scott Shell <scottsh@...rosoft.com>,
Steve Dower <steve.dower@...hon.org>,
Steve Grubb <sgrubb@...hat.com>,
kernel-hardening@...ts.openwall.com,
linux-api@...r.kernel.org,
linux-fsdevel@...r.kernel.org,
linux-integrity@...r.kernel.org,
linux-kernel@...r.kernel.org,
linux-security-module@...r.kernel.org,
Andy Lutomirski <luto@...capital.net>,
Jeff Xu <jeffxu@...omium.org>
Subject: [RFC PATCH v1 2/2] selftests/exec: Add O_DENY_WRITE tests
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 <viro@...iv.linux.org.uk>
Cc: Andy Lutomirski <luto@...capital.net>
Cc: Christian Brauner <brauner@...nel.org>
Cc: Jeff Xu <jeffxu@...omium.org>
Cc: Kees Cook <keescook@...omium.org>
Cc: Paul Moore <paul@...l-moore.com>
Cc: Robert Waite <rowait@...rosoft.com>
Cc: Serge Hallyn <serge@...lyn.com>
Signed-off-by: Mickaël Salaün <mic@...ikod.net>
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 <linux/fcntl.h>
+#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
Powered by blists - more mailing lists