lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<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

Powered by Openwall GNU/*/Linux Powered by OpenVZ