From: "Mickaël Salaün" <mic@digikod.net>
To: Al Viro <viro@zeniv.linux.org.uk>,
Christian Brauner <brauner@kernel.org>,
Kees Cook <keescook@chromium.org>,
Paul Moore <paul@paul-moore.com>, Serge Hallyn <serge@hallyn.com>
Cc: "Mickaël Salaün" <mic@digikod.net>,
"Andy Lutomirski" <luto@kernel.org>,
"Arnd Bergmann" <arnd@arndb.de>,
"Christian Heimes" <christian@python.org>,
"Dmitry Vyukov" <dvyukov@google.com>,
"Elliott Hughes" <enh@google.com>,
"Fan Wu" <wufan@linux.microsoft.com>,
"Florian Weimer" <fweimer@redhat.com>,
"Jann Horn" <jannh@google.com>, "Jeff Xu" <jeffxu@google.com>,
"Jonathan Corbet" <corbet@lwn.net>,
"Jordan R Abrahams" <ajordanr@google.com>,
"Lakshmi Ramasubramanian" <nramas@linux.microsoft.com>,
"Luca Boccassi" <bluca@debian.org>,
"Matt Bobrowski" <mattbobrowski@google.com>,
"Miklos Szeredi" <mszeredi@redhat.com>,
"Mimi Zohar" <zohar@linux.ibm.com>,
"Nicolas Bouchinet" <nicolas.bouchinet@oss.cyber.gouv.fr>,
"Robert Waite" <rowait@microsoft.com>,
"Roberto Sassu" <roberto.sassu@huawei.com>,
"Scott Shell" <scottsh@microsoft.com>,
"Steve Dower" <steve.dower@python.org>,
"Steve Grubb" <sgrubb@redhat.com>,
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" <luto@amacapital.net>,
"Jeff Xu" <jeffxu@chromium.org>
Subject: [RFC PATCH v1 2/2] selftests/exec: Add O_DENY_WRITE tests
Date: Fri, 22 Aug 2025 19:08:00 +0200 [thread overview]
Message-ID: <20250822170800.2116980-3-mic@digikod.net> (raw)
In-Reply-To: <20250822170800.2116980-1-mic@digikod.net>
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@zeniv.linux.org.uk>
Cc: Andy Lutomirski <luto@amacapital.net>
Cc: Christian Brauner <brauner@kernel.org>
Cc: Jeff Xu <jeffxu@chromium.org>
Cc: Kees Cook <keescook@chromium.org>
Cc: Paul Moore <paul@paul-moore.com>
Cc: Robert Waite <rowait@microsoft.com>
Cc: Serge Hallyn <serge@hallyn.com>
Signed-off-by: Mickaël Salaün <mic@digikod.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
next prev parent reply other threads:[~2025-08-22 17:08 UTC|newest]
Thread overview: 38+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-08-22 17:07 [RFC PATCH v1 0/2] Add O_DENY_WRITE (complement AT_EXECVE_CHECK) Mickaël Salaün
2025-08-22 17:07 ` [RFC PATCH v1 1/2] fs: Add O_DENY_WRITE Mickaël Salaün
2025-08-22 19:45 ` Jann Horn
2025-08-24 11:03 ` Mickaël Salaün
2025-08-24 18:04 ` Andy Lutomirski
2025-08-25 9:31 ` Mickaël Salaün
2025-08-25 9:39 ` Florian Weimer
2025-08-26 12:35 ` Mickaël Salaün
2025-08-25 16:43 ` Andy Lutomirski
2025-08-25 18:10 ` Jeff Xu
2025-08-25 17:57 ` Jeff Xu
2025-08-26 12:39 ` Mickaël Salaün
2025-08-26 20:29 ` Jeff Xu
2025-08-27 8:19 ` Mickaël Salaün
2025-08-28 20:17 ` Jeff Xu
2025-08-27 10:18 ` Aleksa Sarai
2025-08-27 10:29 ` Aleksa Sarai
2025-08-22 17:08 ` Mickaël Salaün [this message]
2025-08-26 9:07 ` [RFC PATCH v1 0/2] Add O_DENY_WRITE (complement AT_EXECVE_CHECK) Christian Brauner
2025-08-26 11:23 ` Mickaël Salaün
2025-08-26 12:30 ` Theodore Ts'o
2025-08-26 17:47 ` Mickaël Salaün
2025-08-26 20:50 ` Theodore Ts'o
2025-08-27 8:19 ` Mickaël Salaün
2025-08-27 17:35 ` Andy Lutomirski
2025-08-27 19:07 ` Mickaël Salaün
2025-08-27 20:35 ` Andy Lutomirski
2025-08-28 0:14 ` Aleksa Sarai
2025-08-28 0:32 ` Andy Lutomirski
2025-08-28 0:52 ` Aleksa Sarai
2025-08-28 21:01 ` Serge E. Hallyn
2025-09-01 11:05 ` Jann Horn
2025-09-01 13:18 ` Serge E. Hallyn
2025-09-01 16:01 ` Andy Lutomirski
2025-09-01 9:24 ` Roberto Sassu
2025-09-01 16:25 ` Andy Lutomirski
2025-09-01 17:01 ` Roberto Sassu
2025-09-02 8:57 ` Roberto Sassu
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20250822170800.2116980-3-mic@digikod.net \
--to=mic@digikod.net \
--cc=ajordanr@google.com \
--cc=arnd@arndb.de \
--cc=bluca@debian.org \
--cc=brauner@kernel.org \
--cc=christian@python.org \
--cc=corbet@lwn.net \
--cc=dvyukov@google.com \
--cc=enh@google.com \
--cc=fweimer@redhat.com \
--cc=jannh@google.com \
--cc=jeffxu@chromium.org \
--cc=jeffxu@google.com \
--cc=keescook@chromium.org \
--cc=kernel-hardening@lists.openwall.com \
--cc=linux-api@vger.kernel.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-integrity@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-security-module@vger.kernel.org \
--cc=luto@amacapital.net \
--cc=luto@kernel.org \
--cc=mattbobrowski@google.com \
--cc=mszeredi@redhat.com \
--cc=nicolas.bouchinet@oss.cyber.gouv.fr \
--cc=nramas@linux.microsoft.com \
--cc=paul@paul-moore.com \
--cc=roberto.sassu@huawei.com \
--cc=rowait@microsoft.com \
--cc=scottsh@microsoft.com \
--cc=serge@hallyn.com \
--cc=sgrubb@redhat.com \
--cc=steve.dower@python.org \
--cc=viro@zeniv.linux.org.uk \
--cc=wufan@linux.microsoft.com \
--cc=zohar@linux.ibm.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®