blob: a774d551ae01fc9e02df33d4b3361d05f61f2f40 [file] [log] [blame]
Mike Frysinger4d2a81e2018-01-22 16:43:33 -05001/* Copyright 2018 The Chromium OS Authors. All rights reserved.
2 * Use of this source code is governed by a BSD-style license that can be
3 * found in the LICENSE file.
4 *
5 * Test the minijail0 CLI using gtest.
6 *
7 * Note: We don't verify that the minijail struct was set correctly from these
8 * flags as only libminijail.c knows that definition. If we wanted to improve
9 * this test, we'd have to pull that struct into a common (internal) header.
10 */
11
12#include <stdio.h>
13#include <stdlib.h>
14
15#include <gtest/gtest.h>
16
17#include "libminijail.h"
18#include "minijail0_cli.h"
19
20namespace {
21
22constexpr char kValidUser[] = "nobody";
23constexpr char kValidUid[] = "100";
24constexpr char kValidGroup[] = "users";
25constexpr char kValidGid[] = "100";
26
27class CliTest : public ::testing::Test {
28 protected:
29 virtual void SetUp() {
30 j_ = minijail_new();
31
32 // Most tests do not care about this logic. For the few that do, make
33 // them opt into it so they can validate specifically.
34 elftype_ = ELFDYNAMIC;
35 }
36 virtual void TearDown() {
37 minijail_destroy(j_);
38 }
39
40 // We use a vector of strings rather than const char * pointers because we
41 // need the backing memory to be writable. The CLI might mutate the strings
42 // as it parses things (which is normally permissible with argv).
43 int parse_args_(const std::vector<std::string>& argv, int *exit_immediately,
44 ElfType *elftype) {
45 // Make sure we reset the getopts state when scanning a new argv.
46 optind = 1;
47
48 std::vector<const char *> pargv;
49 pargv.push_back("minijail0");
50 for (const std::string& arg : argv)
51 pargv.push_back(arg.c_str());
52
53 // We grab stdout from parse_args itself as it might dump things we don't
54 // usually care about like help output.
55 testing::internal::CaptureStdout();
56 int ret = parse_args(j_, pargv.size(),
57 const_cast<char* const*>(pargv.data()),
58 exit_immediately, elftype);
59 testing::internal::GetCapturedStdout();
60
61 return ret;
62 }
63
64 int parse_args_(const std::vector<std::string>& argv) {
65 return parse_args_(argv, &exit_immediately_, &elftype_);
66 }
67
68 struct minijail *j_;
69 ElfType elftype_;
70 int exit_immediately_;
71};
72
73} // namespace
74
75// Should exit non-zero when there's no arguments.
76TEST_F(CliTest, no_args) {
77 std::vector<std::string> argv = {};
78 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
79}
80
81// Should exit zero when we asked for help.
82TEST_F(CliTest, help) {
83 std::vector<std::string> argv = {"-h"};
84 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(0), "");
85
86 argv = {"--help"};
87 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(0), "");
88
89 argv = {"-H"};
90 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(0), "");
91}
92
93// Just a simple program to run.
94TEST_F(CliTest, valid_program) {
95 std::vector<std::string> argv = {"/bin/sh"};
96 ASSERT_TRUE(parse_args_(argv));
97}
98
99// Valid calls to the change user option.
100TEST_F(CliTest, valid_set_user) {
101 std::vector<std::string> argv = {"-u", "", "/bin/sh"};
102
103 argv[1] = kValidUser;
104 ASSERT_TRUE(parse_args_(argv));
105
106 argv[1] = kValidUid;
107 ASSERT_TRUE(parse_args_(argv));
108}
109
110// Invalid calls to the change user option.
111TEST_F(CliTest, invalid_set_user) {
112 std::vector<std::string> argv = {"-u", "", "/bin/sh"};
113 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
114
115 argv[1] = "j;lX:J*Pj;oijfs;jdlkjC;j";
116 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
117
118 argv[1] = "1000x";
119 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
120}
121
122// Valid calls to the change group option.
123TEST_F(CliTest, valid_set_group) {
124 std::vector<std::string> argv = {"-g", "", "/bin/sh"};
125
126 argv[1] = kValidGroup;
127 ASSERT_TRUE(parse_args_(argv));
128
129 argv[1] = kValidGid;
130 ASSERT_TRUE(parse_args_(argv));
131}
132
133// Invalid calls to the change group option.
134TEST_F(CliTest, invalid_set_group) {
135 std::vector<std::string> argv = {"-g", "", "/bin/sh"};
136 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
137
138 argv[1] = "j;lX:J*Pj;oijfs;jdlkjC;j";
139 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
140
141 argv[1] = "1000x";
142 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
143}
144
145// Valid calls to the skip securebits option.
146TEST_F(CliTest, valid_skip_securebits) {
147 // An empty string is the same as 0.
148 std::vector<std::string> argv = {"-B", "", "/bin/sh"};
149 ASSERT_TRUE(parse_args_(argv));
150
151 argv[1] = "0xAB";
152 ASSERT_TRUE(parse_args_(argv));
153
154 argv[1] = "1234";
155 ASSERT_TRUE(parse_args_(argv));
156}
157
158// Invalid calls to the skip securebits option.
159TEST_F(CliTest, invalid_skip_securebits) {
160 std::vector<std::string> argv = {"-B", "", "/bin/sh"};
161
162 argv[1] = "xja";
163 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
164}
165
166// Valid calls to the caps option.
167TEST_F(CliTest, valid_caps) {
168 // An empty string is the same as 0.
169 std::vector<std::string> argv = {"-c", "", "/bin/sh"};
170 ASSERT_TRUE(parse_args_(argv));
171
172 argv[1] = "0xAB";
173 ASSERT_TRUE(parse_args_(argv));
174
175 argv[1] = "1234";
176 ASSERT_TRUE(parse_args_(argv));
177}
178
179// Invalid calls to the caps option.
180TEST_F(CliTest, invalid_caps) {
181 std::vector<std::string> argv = {"-c", "", "/bin/sh"};
182
183 argv[1] = "xja";
184 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
185}
186
187// Valid calls to the logging option.
188TEST_F(CliTest, valid_logging) {
189 std::vector<std::string> argv = {"--logging", "", "/bin/sh"};
190
191 // This should list all valid logging targets.
192 const std::vector<std::string> profiles = {
193 "stderr",
194 "syslog",
195 };
196
197 for (const auto profile : profiles) {
198 argv[1] = profile;
199 ASSERT_TRUE(parse_args_(argv));
200 }
201}
202
203// Invalid calls to the logging option.
204TEST_F(CliTest, invalid_logging) {
205 std::vector<std::string> argv = {"--logging", "", "/bin/sh"};
206 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
207
208 argv[1] = "stdout";
209 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
210}
211
212// Valid calls to the rlimit option.
213TEST_F(CliTest, valid_rlimit) {
214 std::vector<std::string> argv = {"-R", "", "/bin/sh"};
215
216 argv[1] = "0,1,2";
217 ASSERT_TRUE(parse_args_(argv));
Luis Hector Chavez7058a2d2018-01-29 08:41:34 -0800218
219 argv[1] = "1,1,unlimited";
220 ASSERT_TRUE(parse_args_(argv));
221
222 argv[1] = "2,unlimited,2";
223 ASSERT_TRUE(parse_args_(argv));
Mike Frysinger4d2a81e2018-01-22 16:43:33 -0500224}
225
226// Invalid calls to the rlimit option.
227TEST_F(CliTest, invalid_rlimit) {
228 std::vector<std::string> argv = {"-R", "", "/bin/sh"};
229 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
230
231 // Missing cur & max.
232 argv[1] = "0";
233 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
234
235 // Missing max.
236 argv[1] = "0,0";
237 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
238
239 // Too many options.
240 argv[1] = "0,0,0,0";
241 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
242
Luis Hector Chavez7058a2d2018-01-29 08:41:34 -0800243 // Non-numeric limits
244 argv[1] = "0,0,invalid-limit";
245 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
246
247 argv[1] = "0,0,0j";
248 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
Mike Frysinger4d2a81e2018-01-22 16:43:33 -0500249}
250
251// Valid calls to the profile option.
252TEST_F(CliTest, valid_profile) {
253 std::vector<std::string> argv = {"--profile", "", "/bin/sh"};
254
255 // This should list all valid profiles.
256 const std::vector<std::string> profiles = {
257 "minimalistic-mountns",
258 };
259
260 for (const auto profile : profiles) {
261 argv[1] = profile;
262 ASSERT_TRUE(parse_args_(argv));
263 }
264}
265
266// Invalid calls to the profile option.
267TEST_F(CliTest, invalid_profile) {
268 std::vector<std::string> argv = {"--profile", "", "/bin/sh"};
269 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
270
271 argv[1] = "random-unknown-profile";
272 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
273}
274
275// Valid calls to the chroot option.
276TEST_F(CliTest, valid_chroot) {
277 std::vector<std::string> argv = {"-C", "/", "/bin/sh"};
278 ASSERT_TRUE(parse_args_(argv));
279}
280
281// Valid calls to the pivot root option.
282TEST_F(CliTest, valid_pivot_root) {
283 std::vector<std::string> argv = {"-P", "/", "/bin/sh"};
284 ASSERT_TRUE(parse_args_(argv));
285}
286
287// We cannot handle multiple options with chroot/profile/pivot root.
288TEST_F(CliTest, conflicting_roots) {
289 std::vector<std::string> argv;
290
291 // Chroot & pivot root.
292 argv = {"-C", "/", "-P", "/", "/bin/sh"};
293 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
294
295 // Chroot & minimalistic-mountns profile.
296 argv = {"-C", "/", "--profile", "minimalistic-mountns", "/bin/sh"};
297 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
298
299 // Pivot root & minimalistic-mountns profile.
300 argv = {"-P", "/", "--profile", "minimalistic-mountns", "/bin/sh"};
301 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
302}
303
304// Valid calls to the uidmap option.
305TEST_F(CliTest, valid_uidmap) {
306 std::vector<std::string> argv = {"-m", "/bin/sh"};
307 // Use a default map (no option from user).
308 ASSERT_TRUE(parse_args_(argv));
309
310 // Use a single map.
311 argv = {"-m0 0 1", "/bin/sh"};
312 ASSERT_TRUE(parse_args_(argv));
313
314 // Multiple maps.
315 argv = {"-m0 0 1,100 100 1", "/bin/sh"};
316 ASSERT_TRUE(parse_args_(argv));
317}
318
319// Valid calls to the gidmap option.
320TEST_F(CliTest, valid_gidmap) {
321 std::vector<std::string> argv = {"-M", "/bin/sh"};
322 // Use a default map (no option from user).
323 ASSERT_TRUE(parse_args_(argv));
324
325 // Use a single map.
326 argv = {"-M0 0 1", "/bin/sh"};
327 ASSERT_TRUE(parse_args_(argv));
328
329 // Multiple maps.
330 argv = {"-M0 0 1,100 100 1", "/bin/sh"};
331 ASSERT_TRUE(parse_args_(argv));
332}
333
334// Invalid calls to the uidmap/gidmap options.
335// Note: Can't really test these as all validation is delayed/left to the
336// runtime kernel. Minijail will simply write verbatim what the user gave
337// it to the corresponding /proc/.../[ug]id_map.
338
339// Valid calls to the binding option.
340TEST_F(CliTest, valid_binding) {
341 std::vector<std::string> argv = {"-v", "-b", "", "/bin/sh"};
342
343 // Dest & writable are optional.
344 argv[1] = "/";
345 ASSERT_TRUE(parse_args_(argv));
346
347 // Writable is optional.
348 argv[1] = "/,/";
349 ASSERT_TRUE(parse_args_(argv));
350
351 // Writable is an integer.
352 argv[1] = "/,/,0";
353 ASSERT_TRUE(parse_args_(argv));
354 argv[1] = "/,/,1";
355 ASSERT_TRUE(parse_args_(argv));
356
357 // Dest is optional.
358 argv[1] = "/,,0";
359 ASSERT_TRUE(parse_args_(argv));
360}
361
362// Invalid calls to the binding option.
363TEST_F(CliTest, invalid_binding) {
364 std::vector<std::string> argv = {"-v", "-b", "", "/bin/sh"};
365
366 // Missing source.
367 argv[2] = "";
368 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
369
370 // Too many args.
371 argv[2] = "/,/,0,what";
372 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
373
374 // Missing mount namespace/etc...
375 argv = {"-b", "/", "/bin/sh"};
376 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
377}
378
379// Valid calls to the mount option.
380TEST_F(CliTest, valid_mount) {
381 std::vector<std::string> argv = {"-v", "-k", "", "/bin/sh"};
382
383 // Flags & data are optional.
384 argv[2] = "none,/,none";
385 ASSERT_TRUE(parse_args_(argv));
386
387 // Data is optional.
388 argv[2] = "none,/,none,0xe";
389 ASSERT_TRUE(parse_args_(argv));
390
391 // Flags are optional.
392 argv[2] = "none,/,none,,mode=755";
393 ASSERT_TRUE(parse_args_(argv));
Mike Frysinger4f3e09f2018-01-24 18:01:16 -0500394
395 // Multiple data options to the kernel.
396 argv[2] = "none,/,none,0xe,mode=755,uid=0,gid=10";
397 ASSERT_TRUE(parse_args_(argv));
Mike Frysinger4d2a81e2018-01-22 16:43:33 -0500398}
399
400// Invalid calls to the mount option.
401TEST_F(CliTest, invalid_mount) {
402 std::vector<std::string> argv = {"-v", "-k", "", "/bin/sh"};
403
404 // Missing source.
405 argv[2] = "";
406 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
407
408 // Missing dest.
409 argv[2] = "none";
410 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
411
412 // Missing type.
413 argv[2] = "none,/";
414 ASSERT_EXIT(parse_args_(argv), testing::ExitedWithCode(1), "");
415}