@@ -1039,7 +1039,7 @@ static int __init fcntl_init(void)
* Exceptions: O_NONBLOCK is a two bit define on parisc; O_NDELAY
* is defined as O_NONBLOCK on some platforms and not on others.
*/
- BUILD_BUG_ON(21 - 1 /* for O_RDONLY being 0 */ !=
+ BUILD_BUG_ON(22 - 1 /* for O_RDONLY being 0 */ !=
HWEIGHT32(
(VALID_OPEN_FLAGS & ~(O_NONBLOCK | O_NDELAY)) |
__FMODE_EXEC | __FMODE_NONOTIFY));
@@ -827,22 +827,23 @@ void do_close_on_exec(struct files_struct *files)
/* exec unshares first */
spin_lock(&files->file_lock);
for (i = 0; ; i++) {
+ int j;
unsigned long set;
unsigned fd = i * BITS_PER_LONG;
fdt = files_fdtable(files);
if (fd >= fdt->max_fds)
break;
set = fdt->close_on_exec[i];
- if (!set)
- continue;
fdt->close_on_exec[i] = 0;
- for ( ; set ; fd++, set >>= 1) {
- struct file *file;
- if (!(set & 1))
- continue;
- file = fdt->fd[fd];
+ for (j = 0; j < BITS_PER_LONG; j++, fd++, set >>= 1) {
+ struct file *file = fdt->fd[fd];
if (!file)
continue;
+ /* Close all cred-allow files. */
+ if (file->f_flags & O_CRED_ALLOW)
+ set |= 1;
+ if (!(set & 1))
+ continue;
rcu_assign_pointer(fdt->fd[fd], NULL);
__put_unused_fd(files, fd);
spin_unlock(&files->file_lock);
@@ -10,7 +10,7 @@
(O_RDONLY | O_WRONLY | O_RDWR | O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC | \
O_APPEND | O_NDELAY | O_NONBLOCK | __O_SYNC | O_DSYNC | \
FASYNC | O_DIRECT | O_LARGEFILE | O_DIRECTORY | O_NOFOLLOW | \
- O_NOATIME | O_CLOEXEC | O_PATH | __O_TMPFILE)
+ O_NOATIME | O_CLOEXEC | O_PATH | __O_TMPFILE | O_CRED_ALLOW)
/* List of all valid flags for the how->resolve argument: */
#define VALID_RESOLVE_FLAGS \
@@ -89,6 +89,11 @@
#define __O_TMPFILE 020000000
#endif
+#ifndef O_CRED_ALLOW
+/* On parisc bit 23 is taken. On alpha bit 24 is also taken. Try bit 25. */
+#define O_CRED_ALLOW 0200000000
+#endif
+
/* a horrid kludge trying to make sure that this will fail on old kernels */
#define O_TMPFILE (__O_TMPFILE | O_DIRECTORY)
@@ -111,6 +111,11 @@ static int scm_fp_copy(struct cmsghdr *cmsg, struct scm_fp_list **fplp)
fput(file);
return -EINVAL;
}
+ /* don't allow files with creds */
+ if (file->f_flags & O_CRED_ALLOW) {
+ fput(file);
+ return -EPERM;
+ }
if (unix_get_socket(file))
fpl->count_unix++;
@@ -1,7 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
CFLAGS += -g $(KHDR_INCLUDES)
-TEST_GEN_PROGS := close_range_test
+TEST_GEN_PROGS := close_range_test cred_allow
include ../lib.mk
new file mode 100644
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <errno.h>
+#include <fcntl.h>
+#include <sched.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <time.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "../kselftest.h"
+
+#ifndef O_CRED_ALLOW
+#define O_CRED_ALLOW 0x2000000
+#endif
+
+enum { FD_NORM, FD_CE, FD_CA, FD_MAX };
+
+static int is_opened(int n)
+{
+ char buf[256];
+
+ snprintf(buf, sizeof(buf), "/proc/self/fd/%d", n);
+ return (access(buf, F_OK) == 0);
+}
+
+/* Sends an FD on a UNIX socket. Returns 0 on success or -errno. */
+static int send_fd(int usock, int fd_tx)
+{
+ union {
+ /* Aligned ancillary data buffer. */
+ char buf[CMSG_SPACE(sizeof(fd_tx))];
+ struct cmsghdr _align;
+ } cmsg_tx = {};
+ char data_tx = '.';
+ struct iovec io = {
+ .iov_base = &data_tx,
+ .iov_len = sizeof(data_tx),
+ };
+ struct msghdr msg = {
+ .msg_iov = &io,
+ .msg_iovlen = 1,
+ .msg_control = &cmsg_tx.buf,
+ .msg_controllen = sizeof(cmsg_tx.buf),
+ };
+ struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
+
+ cmsg->cmsg_len = CMSG_LEN(sizeof(fd_tx));
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ memcpy(CMSG_DATA(cmsg), &fd_tx, sizeof(fd_tx));
+
+ if (sendmsg(usock, &msg, 0) < 0)
+ return -errno;
+ return 0;
+}
+
+int main(int argc, char *argv[], char *env[])
+{
+ int status;
+ int err;
+ pid_t pid;
+ int socket_fds[2];
+ int fds[FD_MAX];
+#define NFD(n) ((n) + 3)
+#define FD_OK(n) (NFD(n) == fds[n] && is_opened(fds[n]))
+
+ if (argc > 1 && strcmp(argv[1], "--child") == 0) {
+ int nfd = 0;
+ ksft_print_msg("we are child\n");
+ ksft_set_plan(3);
+ nfd += is_opened(NFD(FD_NORM));
+ ksft_test_result(nfd == 1, "normal fd opened\n");
+ nfd += is_opened(NFD(FD_CE));
+ ksft_test_result(nfd == 1, "O_CLOEXEC fd closed\n");
+ nfd += is_opened(NFD(FD_CA));
+ ksft_test_result(nfd == 1, "O_CRED_ALLOW fd closed\n");
+ /* exit with non-zero status propagates to parent's failure */
+ ksft_finished();
+ return 0;
+ }
+
+ ksft_set_plan(7);
+
+ fds[FD_NORM] = open("/proc/self/exe", O_RDONLY);
+ fds[FD_CE] = open("/proc/self/exe", O_RDONLY | O_CLOEXEC);
+ fds[FD_CA] = open("/proc/self/exe", O_RDONLY | O_CRED_ALLOW);
+ ksft_test_result(FD_OK(FD_NORM), "regular open\n");
+ ksft_test_result(FD_OK(FD_CE), "O_CLOEXEC open\n");
+ ksft_test_result(FD_OK(FD_CA), "O_CRED_ALLOW open\n");
+
+ err = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, socket_fds);
+ if (err) {
+ ksft_perror("socketpair() failed");
+ ksft_exit_fail_msg("socketpair\n");
+ return 1;
+ }
+ err = send_fd(socket_fds[0], fds[FD_NORM]);
+ ksft_test_result(err == 0, "normal fd sent\n");
+ err = send_fd(socket_fds[0], fds[FD_CE]);
+ ksft_test_result(err == 0, "O_CLOEXEC fd sent\n");
+ err = send_fd(socket_fds[0], fds[FD_CA]);
+ ksft_test_result(err == -EPERM, "O_CRED_ALLOW fd not sent, EPERM\n");
+ close(socket_fds[0]);
+ close(socket_fds[1]);
+
+ pid = fork();
+ if (pid < 0) {
+ ksft_perror("fork() failed");
+ ksft_exit_fail_msg("fork\n");
+ return 1;
+ }
+
+ if (pid == 0) {
+ char *cargv[] = {"cred_allow", "--child", NULL};
+
+ execve("/proc/self/exe", cargv, env);
+ ksft_perror("execve() failed");
+ ksft_exit_fail_msg("execve\n");
+ return 1;
+ }
+
+ if (waitpid(pid, &status, 0) != pid) {
+ ksft_perror("waitpid() failed");
+ ksft_exit_fail_msg("waitpid\n");
+ return 1;
+ }
+ ksft_print_msg("back to parent\n");
+
+ ksft_test_result(status == 0, "child success\n");
+
+ ksft_finished();
+ return 0;
+}
This flag prevents an fd from being passed via unix socket, and makes it to be always closed on exec(). Selftest is added to check for both properties. It is needed for the subsequent OA2_CRED_INHERIT addition, to work as an "opt-in" for the new cred-inherit functionality. Without using O_CRED_ALLOW when opening dir fd, OA2_CRED_INHERIT is going to return EPERM. Signed-off-by: Stas Sergeev <stsp2@yandex.ru> CC: Eric Biederman <ebiederm@xmission.com> CC: Alexander Viro <viro@zeniv.linux.org.uk> CC: Christian Brauner <brauner@kernel.org> CC: Jan Kara <jack@suse.cz> CC: Andy Lutomirski <luto@kernel.org> CC: David Laight <David.Laight@ACULAB.COM> CC: Arnd Bergmann <arnd@arndb.de> CC: "David S. Miller" <davem@davemloft.net> CC: Eric Dumazet <edumazet@google.com> CC: Jakub Kicinski <kuba@kernel.org> CC: Paolo Abeni <pabeni@redhat.com> CC: Jens Axboe <axboe@kernel.dk> CC: Kuniyuki Iwashima <kuniyu@amazon.com> CC: Pavel Begunkov <asml.silence@gmail.com> CC: linux-arch@vger.kernel.org CC: netdev@vger.kernel.org CC: linux-fsdevel@vger.kernel.org CC: linux-kernel@vger.kernel.org CC: linux-api@vger.kernel.org --- fs/fcntl.c | 2 +- fs/file.c | 15 +-- include/linux/fcntl.h | 2 +- include/uapi/asm-generic/fcntl.h | 5 + net/core/scm.c | 5 + tools/testing/selftests/core/Makefile | 2 +- tools/testing/selftests/core/cred_allow.c | 139 ++++++++++++++++++++++ 7 files changed, 160 insertions(+), 10 deletions(-) create mode 100644 tools/testing/selftests/core/cred_allow.c