summaryrefslogtreecommitdiff
path: root/tools/testing/selftests/clone3/clone3_cap_checkpoint_restore.c
blob: 31b56d6256550b62aca41495f6d716d17cd5f54a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
// SPDX-License-Identifier: GPL-2.0

/*
 * Based on Christian Brauner's clone3() example.
 * These tests are assuming to be running in the host's
 * PID namespace.
 */

/* capabilities related code based on selftests/bpf/test_verifier.c */

#define _GNU_SOURCE
#include <errno.h>
#include <linux/types.h>
#include <linux/sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <sys/capability.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sched.h>

#include "../kselftest_harness.h"
#include "clone3_selftests.h"

#define MAX_PID_NS_LEVEL 32

static void child_exit(int ret)
{
	fflush(stdout);
	fflush(stderr);
	_exit(ret);
}

static int call_clone3_set_tid(struct __test_metadata *_metadata,
			       pid_t *set_tid, size_t set_tid_size)
{
	int status;
	pid_t pid = -1;

	struct __clone_args args = {
		.exit_signal = SIGCHLD,
		.set_tid = ptr_to_u64(set_tid),
		.set_tid_size = set_tid_size,
	};

	pid = sys_clone3(&args, sizeof(args));
	if (pid < 0) {
		TH_LOG("%s - Failed to create new process", strerror(errno));
		return -errno;
	}

	if (pid == 0) {
		int ret;
		char tmp = 0;

		TH_LOG("I am the child, my PID is %d (expected %d)", getpid(), set_tid[0]);

		if (set_tid[0] != getpid())
			child_exit(EXIT_FAILURE);
		child_exit(EXIT_SUCCESS);
	}

	TH_LOG("I am the parent (%d). My child's pid is %d", getpid(), pid);

	if (waitpid(pid, &status, 0) < 0) {
		TH_LOG("Child returned %s", strerror(errno));
		return -errno;
	}

	if (!WIFEXITED(status))
		return -1;

	return WEXITSTATUS(status);
}

static int test_clone3_set_tid(struct __test_metadata *_metadata,
			       pid_t *set_tid, size_t set_tid_size)
{
	int ret;

	TH_LOG("[%d] Trying clone3() with CLONE_SET_TID to %d", getpid(), set_tid[0]);
	ret = call_clone3_set_tid(_metadata, set_tid, set_tid_size);
	TH_LOG("[%d] clone3() with CLONE_SET_TID %d says:%d", getpid(), set_tid[0], ret);
	return ret;
}

struct libcap {
	struct __user_cap_header_struct hdr;
	struct __user_cap_data_struct data[2];
};

static int set_capability(void)
{
	cap_value_t cap_values[] = { CAP_SETUID, CAP_SETGID };
	struct libcap *cap;
	int ret = -1;
	cap_t caps;

	caps = cap_get_proc();
	if (!caps) {
		perror("cap_get_proc");
		return -1;
	}

	/* Drop all capabilities */
	if (cap_clear(caps)) {
		perror("cap_clear");
		goto out;
	}

	cap_set_flag(caps, CAP_EFFECTIVE, 2, cap_values, CAP_SET);
	cap_set_flag(caps, CAP_PERMITTED, 2, cap_values, CAP_SET);

	cap = (struct libcap *) caps;

	/* 40 -> CAP_CHECKPOINT_RESTORE */
	cap->data[1].effective |= 1 << (40 - 32);
	cap->data[1].permitted |= 1 << (40 - 32);

	if (cap_set_proc(caps)) {
		perror("cap_set_proc");
		goto out;
	}
	ret = 0;
out:
	if (cap_free(caps))
		perror("cap_free");
	return ret;
}

TEST(clone3_cap_checkpoint_restore)
{
	pid_t pid;
	int status;
	int ret = 0;
	pid_t set_tid[1];

	test_clone3_supported();

	EXPECT_EQ(getuid(), 0)
		SKIP(return, "Skipping all tests as non-root");

	memset(&set_tid, 0, sizeof(set_tid));

	/* Find the current active PID */
	pid = fork();
	if (pid == 0) {
		TH_LOG("Child has PID %d", getpid());
		child_exit(EXIT_SUCCESS);
	}
	ASSERT_GT(waitpid(pid, &status, 0), 0)
		TH_LOG("Waiting for child %d failed", pid);

	/* After the child has finished, its PID should be free. */
	set_tid[0] = pid;

	ASSERT_EQ(set_capability(), 0)
		TH_LOG("Could not set CAP_CHECKPOINT_RESTORE");

	ASSERT_EQ(prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0), 0);

	EXPECT_EQ(setgid(65534), 0)
		TH_LOG("Failed to setgid(65534)");
	ASSERT_EQ(setuid(65534), 0);

	set_tid[0] = pid;
	/* This would fail without CAP_CHECKPOINT_RESTORE */
	ASSERT_EQ(test_clone3_set_tid(_metadata, set_tid, 1), -EPERM);
	ASSERT_EQ(set_capability(), 0)
		TH_LOG("Could not set CAP_CHECKPOINT_RESTORE");
	/* This should work as we have CAP_CHECKPOINT_RESTORE as non-root */
	ASSERT_EQ(test_clone3_set_tid(_metadata, set_tid, 1), 0);
}

TEST_HARNESS_MAIN