162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright IBM Corporation, 2021
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Author: Mike Rapoport <rppt@linux.ibm.com>
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#define _GNU_SOURCE
962306a36Sopenharmony_ci#include <sys/uio.h>
1062306a36Sopenharmony_ci#include <sys/mman.h>
1162306a36Sopenharmony_ci#include <sys/wait.h>
1262306a36Sopenharmony_ci#include <sys/types.h>
1362306a36Sopenharmony_ci#include <sys/ptrace.h>
1462306a36Sopenharmony_ci#include <sys/syscall.h>
1562306a36Sopenharmony_ci#include <sys/resource.h>
1662306a36Sopenharmony_ci#include <sys/capability.h>
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#include <stdlib.h>
1962306a36Sopenharmony_ci#include <string.h>
2062306a36Sopenharmony_ci#include <unistd.h>
2162306a36Sopenharmony_ci#include <errno.h>
2262306a36Sopenharmony_ci#include <stdio.h>
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci#include "../kselftest.h"
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci#define fail(fmt, ...) ksft_test_result_fail(fmt, ##__VA_ARGS__)
2762306a36Sopenharmony_ci#define pass(fmt, ...) ksft_test_result_pass(fmt, ##__VA_ARGS__)
2862306a36Sopenharmony_ci#define skip(fmt, ...) ksft_test_result_skip(fmt, ##__VA_ARGS__)
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci#ifdef __NR_memfd_secret
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci#define PATTERN	0x55
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_cistatic const int prot = PROT_READ | PROT_WRITE;
3562306a36Sopenharmony_cistatic const int mode = MAP_SHARED;
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_cistatic unsigned long page_size;
3862306a36Sopenharmony_cistatic unsigned long mlock_limit_cur;
3962306a36Sopenharmony_cistatic unsigned long mlock_limit_max;
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_cistatic int memfd_secret(unsigned int flags)
4262306a36Sopenharmony_ci{
4362306a36Sopenharmony_ci	return syscall(__NR_memfd_secret, flags);
4462306a36Sopenharmony_ci}
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_cistatic void test_file_apis(int fd)
4762306a36Sopenharmony_ci{
4862306a36Sopenharmony_ci	char buf[64];
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci	if ((read(fd, buf, sizeof(buf)) >= 0) ||
5162306a36Sopenharmony_ci	    (write(fd, buf, sizeof(buf)) >= 0) ||
5262306a36Sopenharmony_ci	    (pread(fd, buf, sizeof(buf), 0) >= 0) ||
5362306a36Sopenharmony_ci	    (pwrite(fd, buf, sizeof(buf), 0) >= 0))
5462306a36Sopenharmony_ci		fail("unexpected file IO\n");
5562306a36Sopenharmony_ci	else
5662306a36Sopenharmony_ci		pass("file IO is blocked as expected\n");
5762306a36Sopenharmony_ci}
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_cistatic void test_mlock_limit(int fd)
6062306a36Sopenharmony_ci{
6162306a36Sopenharmony_ci	size_t len;
6262306a36Sopenharmony_ci	char *mem;
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	len = mlock_limit_cur;
6562306a36Sopenharmony_ci	if (len % page_size != 0)
6662306a36Sopenharmony_ci		len = (len/page_size) * page_size;
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	mem = mmap(NULL, len, prot, mode, fd, 0);
6962306a36Sopenharmony_ci	if (mem == MAP_FAILED) {
7062306a36Sopenharmony_ci		fail("unable to mmap secret memory\n");
7162306a36Sopenharmony_ci		return;
7262306a36Sopenharmony_ci	}
7362306a36Sopenharmony_ci	munmap(mem, len);
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	len = mlock_limit_max * 2;
7662306a36Sopenharmony_ci	mem = mmap(NULL, len, prot, mode, fd, 0);
7762306a36Sopenharmony_ci	if (mem != MAP_FAILED) {
7862306a36Sopenharmony_ci		fail("unexpected mlock limit violation\n");
7962306a36Sopenharmony_ci		munmap(mem, len);
8062306a36Sopenharmony_ci		return;
8162306a36Sopenharmony_ci	}
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	pass("mlock limit is respected\n");
8462306a36Sopenharmony_ci}
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_cistatic void try_process_vm_read(int fd, int pipefd[2])
8762306a36Sopenharmony_ci{
8862306a36Sopenharmony_ci	struct iovec liov, riov;
8962306a36Sopenharmony_ci	char buf[64];
9062306a36Sopenharmony_ci	char *mem;
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	if (read(pipefd[0], &mem, sizeof(mem)) < 0) {
9362306a36Sopenharmony_ci		fail("pipe write: %s\n", strerror(errno));
9462306a36Sopenharmony_ci		exit(KSFT_FAIL);
9562306a36Sopenharmony_ci	}
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci	liov.iov_len = riov.iov_len = sizeof(buf);
9862306a36Sopenharmony_ci	liov.iov_base = buf;
9962306a36Sopenharmony_ci	riov.iov_base = mem;
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	if (process_vm_readv(getppid(), &liov, 1, &riov, 1, 0) < 0) {
10262306a36Sopenharmony_ci		if (errno == ENOSYS)
10362306a36Sopenharmony_ci			exit(KSFT_SKIP);
10462306a36Sopenharmony_ci		exit(KSFT_PASS);
10562306a36Sopenharmony_ci	}
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	exit(KSFT_FAIL);
10862306a36Sopenharmony_ci}
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_cistatic void try_ptrace(int fd, int pipefd[2])
11162306a36Sopenharmony_ci{
11262306a36Sopenharmony_ci	pid_t ppid = getppid();
11362306a36Sopenharmony_ci	int status;
11462306a36Sopenharmony_ci	char *mem;
11562306a36Sopenharmony_ci	long ret;
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	if (read(pipefd[0], &mem, sizeof(mem)) < 0) {
11862306a36Sopenharmony_ci		perror("pipe write");
11962306a36Sopenharmony_ci		exit(KSFT_FAIL);
12062306a36Sopenharmony_ci	}
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	ret = ptrace(PTRACE_ATTACH, ppid, 0, 0);
12362306a36Sopenharmony_ci	if (ret) {
12462306a36Sopenharmony_ci		perror("ptrace_attach");
12562306a36Sopenharmony_ci		exit(KSFT_FAIL);
12662306a36Sopenharmony_ci	}
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	ret = waitpid(ppid, &status, WUNTRACED);
12962306a36Sopenharmony_ci	if ((ret != ppid) || !(WIFSTOPPED(status))) {
13062306a36Sopenharmony_ci		fprintf(stderr, "weird waitppid result %ld stat %x\n",
13162306a36Sopenharmony_ci			ret, status);
13262306a36Sopenharmony_ci		exit(KSFT_FAIL);
13362306a36Sopenharmony_ci	}
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	if (ptrace(PTRACE_PEEKDATA, ppid, mem, 0))
13662306a36Sopenharmony_ci		exit(KSFT_PASS);
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci	exit(KSFT_FAIL);
13962306a36Sopenharmony_ci}
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_cistatic void check_child_status(pid_t pid, const char *name)
14262306a36Sopenharmony_ci{
14362306a36Sopenharmony_ci	int status;
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	waitpid(pid, &status, 0);
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	if (WIFEXITED(status) && WEXITSTATUS(status) == KSFT_SKIP) {
14862306a36Sopenharmony_ci		skip("%s is not supported\n", name);
14962306a36Sopenharmony_ci		return;
15062306a36Sopenharmony_ci	}
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	if ((WIFEXITED(status) && WEXITSTATUS(status) == KSFT_PASS) ||
15362306a36Sopenharmony_ci	    WIFSIGNALED(status)) {
15462306a36Sopenharmony_ci		pass("%s is blocked as expected\n", name);
15562306a36Sopenharmony_ci		return;
15662306a36Sopenharmony_ci	}
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci	fail("%s: unexpected memory access\n", name);
15962306a36Sopenharmony_ci}
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_cistatic void test_remote_access(int fd, const char *name,
16262306a36Sopenharmony_ci			       void (*func)(int fd, int pipefd[2]))
16362306a36Sopenharmony_ci{
16462306a36Sopenharmony_ci	int pipefd[2];
16562306a36Sopenharmony_ci	pid_t pid;
16662306a36Sopenharmony_ci	char *mem;
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci	if (pipe(pipefd)) {
16962306a36Sopenharmony_ci		fail("pipe failed: %s\n", strerror(errno));
17062306a36Sopenharmony_ci		return;
17162306a36Sopenharmony_ci	}
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	pid = fork();
17462306a36Sopenharmony_ci	if (pid < 0) {
17562306a36Sopenharmony_ci		fail("fork failed: %s\n", strerror(errno));
17662306a36Sopenharmony_ci		return;
17762306a36Sopenharmony_ci	}
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci	if (pid == 0) {
18062306a36Sopenharmony_ci		func(fd, pipefd);
18162306a36Sopenharmony_ci		return;
18262306a36Sopenharmony_ci	}
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci	mem = mmap(NULL, page_size, prot, mode, fd, 0);
18562306a36Sopenharmony_ci	if (mem == MAP_FAILED) {
18662306a36Sopenharmony_ci		fail("Unable to mmap secret memory\n");
18762306a36Sopenharmony_ci		return;
18862306a36Sopenharmony_ci	}
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci	ftruncate(fd, page_size);
19162306a36Sopenharmony_ci	memset(mem, PATTERN, page_size);
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	if (write(pipefd[1], &mem, sizeof(mem)) < 0) {
19462306a36Sopenharmony_ci		fail("pipe write: %s\n", strerror(errno));
19562306a36Sopenharmony_ci		return;
19662306a36Sopenharmony_ci	}
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci	check_child_status(pid, name);
19962306a36Sopenharmony_ci}
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_cistatic void test_process_vm_read(int fd)
20262306a36Sopenharmony_ci{
20362306a36Sopenharmony_ci	test_remote_access(fd, "process_vm_read", try_process_vm_read);
20462306a36Sopenharmony_ci}
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_cistatic void test_ptrace(int fd)
20762306a36Sopenharmony_ci{
20862306a36Sopenharmony_ci	test_remote_access(fd, "ptrace", try_ptrace);
20962306a36Sopenharmony_ci}
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_cistatic int set_cap_limits(rlim_t max)
21262306a36Sopenharmony_ci{
21362306a36Sopenharmony_ci	struct rlimit new;
21462306a36Sopenharmony_ci	cap_t cap = cap_init();
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	new.rlim_cur = max;
21762306a36Sopenharmony_ci	new.rlim_max = max;
21862306a36Sopenharmony_ci	if (setrlimit(RLIMIT_MEMLOCK, &new)) {
21962306a36Sopenharmony_ci		perror("setrlimit() returns error");
22062306a36Sopenharmony_ci		return -1;
22162306a36Sopenharmony_ci	}
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_ci	/* drop capabilities including CAP_IPC_LOCK */
22462306a36Sopenharmony_ci	if (cap_set_proc(cap)) {
22562306a36Sopenharmony_ci		perror("cap_set_proc() returns error");
22662306a36Sopenharmony_ci		return -2;
22762306a36Sopenharmony_ci	}
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_ci	return 0;
23062306a36Sopenharmony_ci}
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_cistatic void prepare(void)
23362306a36Sopenharmony_ci{
23462306a36Sopenharmony_ci	struct rlimit rlim;
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci	page_size = sysconf(_SC_PAGE_SIZE);
23762306a36Sopenharmony_ci	if (!page_size)
23862306a36Sopenharmony_ci		ksft_exit_fail_msg("Failed to get page size %s\n",
23962306a36Sopenharmony_ci				   strerror(errno));
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_ci	if (getrlimit(RLIMIT_MEMLOCK, &rlim))
24262306a36Sopenharmony_ci		ksft_exit_fail_msg("Unable to detect mlock limit: %s\n",
24362306a36Sopenharmony_ci				   strerror(errno));
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	mlock_limit_cur = rlim.rlim_cur;
24662306a36Sopenharmony_ci	mlock_limit_max = rlim.rlim_max;
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci	printf("page_size: %ld, mlock.soft: %ld, mlock.hard: %ld\n",
24962306a36Sopenharmony_ci	       page_size, mlock_limit_cur, mlock_limit_max);
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_ci	if (page_size > mlock_limit_cur)
25262306a36Sopenharmony_ci		mlock_limit_cur = page_size;
25362306a36Sopenharmony_ci	if (page_size > mlock_limit_max)
25462306a36Sopenharmony_ci		mlock_limit_max = page_size;
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ci	if (set_cap_limits(mlock_limit_max))
25762306a36Sopenharmony_ci		ksft_exit_fail_msg("Unable to set mlock limit: %s\n",
25862306a36Sopenharmony_ci				   strerror(errno));
25962306a36Sopenharmony_ci}
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_ci#define NUM_TESTS 4
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_ciint main(int argc, char *argv[])
26462306a36Sopenharmony_ci{
26562306a36Sopenharmony_ci	int fd;
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_ci	prepare();
26862306a36Sopenharmony_ci
26962306a36Sopenharmony_ci	ksft_print_header();
27062306a36Sopenharmony_ci	ksft_set_plan(NUM_TESTS);
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_ci	fd = memfd_secret(0);
27362306a36Sopenharmony_ci	if (fd < 0) {
27462306a36Sopenharmony_ci		if (errno == ENOSYS)
27562306a36Sopenharmony_ci			ksft_exit_skip("memfd_secret is not supported\n");
27662306a36Sopenharmony_ci		else
27762306a36Sopenharmony_ci			ksft_exit_fail_msg("memfd_secret failed: %s\n",
27862306a36Sopenharmony_ci					   strerror(errno));
27962306a36Sopenharmony_ci	}
28062306a36Sopenharmony_ci
28162306a36Sopenharmony_ci	test_mlock_limit(fd);
28262306a36Sopenharmony_ci	test_file_apis(fd);
28362306a36Sopenharmony_ci	test_process_vm_read(fd);
28462306a36Sopenharmony_ci	test_ptrace(fd);
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_ci	close(fd);
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_ci	ksft_finished();
28962306a36Sopenharmony_ci}
29062306a36Sopenharmony_ci
29162306a36Sopenharmony_ci#else /* __NR_memfd_secret */
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_ciint main(int argc, char *argv[])
29462306a36Sopenharmony_ci{
29562306a36Sopenharmony_ci	printf("skip: skipping memfd_secret test (missing __NR_memfd_secret)\n");
29662306a36Sopenharmony_ci	return KSFT_SKIP;
29762306a36Sopenharmony_ci}
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ci#endif /* __NR_memfd_secret */
300