blob: c074e449584d507a1d58cd4f63de51a3d0f0b2e0 [file] [log] [blame]
Alexey Kodanev18f67672013-05-07 20:07:32 +04001/*
2 * Copyright (c) 2013 Oracle and/or its affiliates. All Rights Reserved.
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
Alexey Kodanev20866912013-06-11 13:56:10 +04006 * published by the Free Software Foundation; either version 2 of
7 * the License, or (at your option) any later version.
Alexey Kodanev18f67672013-05-07 20:07:32 +04008 *
9 * This program is distributed in the hope that it would be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write the Free Software Foundation,
16 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17 *
18 * Author:
19 * Alexey Kodanev <alexey.kodanev@oracle.com>
20 *
21 * Test checks following preconditions:
22 *
23 * Symlinks
24 * ---------
25 * Users who own sticky world-writable directory can't follow symlinks
26 * inside that directory if their don't own ones. All other users can follow.
27 *
28 * Hardlinks
29 * ---------
30 * Hard links restriction applies only to non-privileged users. Only
31 * non-privileged user can't create hard links to files if he isn't owner
32 * of the file or he doesn't have write access to the file.
33 */
34
Markos Chandras2067f152013-05-15 15:17:44 +010035#define _GNU_SOURCE
Alexey Kodanev18f67672013-05-07 20:07:32 +040036#include <sys/types.h>
37#include <sys/stat.h>
38#include <pwd.h>
39#include <unistd.h>
40#include <stdio.h>
41#include <stdlib.h>
42#include <string.h>
43#include <signal.h>
44
45#include "test.h"
Alexey Kodanev18f67672013-05-07 20:07:32 +040046#include "safe_macros.h"
Stanislav Kholmanskikh90d19012013-11-06 12:23:07 +040047#include "safe_file_ops.h"
Alexey Kodanev18f67672013-05-07 20:07:32 +040048
49char *TCID = "prot_hsymlinks";
50int TST_TOTAL = 396;
51
52/* create 3 files and 1 dir in each base dir */
53#define MAX_FILES_CREATED 4
54#define MAX_PATH 128
55#define MAX_CMD_LEN 64
56#define MAX_USER_NAME 16
57
58enum {
59 ROOT = 0,
60 TEST_USER,
61 USERS_NUM
62};
63
64#define BASE_DIR_NUM (USERS_NUM + 1)
65/*
66 * max test files and directories
67 * that will be created during the test
68 * is't not include symlinks and hardlinks
69 * and base directories
70 */
71#define MAX_ENTITIES (MAX_FILES_CREATED * BASE_DIR_NUM)
72
73struct dir_params {
74 char path[MAX_PATH];
75 int world_writable;
76 int sticky;
77 int owner;
78};
79
80static struct dir_params bdirs[BASE_DIR_NUM];
81
82static const char file_ext[] = ".hs";
83
84enum {
85 IS_FILE = 0,
86 IS_DIRECTORY,
87};
88
89struct user_file {
90 char path[MAX_PATH];
91 int is_dir;
92};
93
94struct test_user {
95 char name[MAX_USER_NAME];
96 struct user_file file[MAX_ENTITIES];
97 int num;
98};
99
100static struct test_user users[USERS_NUM];
101
102struct link_info {
103 char path[MAX_PATH];
104 int owner;
105 int source_owner;
106 int in_world_write;
107 int in_sticky;
108 int is_dir;
109 int dir_owner;
110};
111
112/* test flags */
113enum {
114 CANNOT_FOLLOW = -1,
115 CAN_FOLLOW = 0,
116};
117
118enum {
119 CANNOT_CREATE = -1,
120 CAN_CREATE = 0,
121};
122
123static char *tmp_user_name;
124static char *default_user = "hsym";
125static int nflag;
126static int skip_cleanup;
127
128static const option_t options[] = {
129 {"u:", &nflag, &tmp_user_name}, /* -u #user name */
130 {"s", &skip_cleanup, NULL},
131 {NULL, NULL, NULL}
132};
133/* full length of the test tmpdir path in /tmp */
134static size_t cwd_offset;
135
136static const char hrdlink_proc_path[] = "/proc/sys/fs/protected_hardlinks";
137static const char symlink_proc_path[] = "/proc/sys/fs/protected_symlinks";
138
139static void help(void);
140static void setup(int argc, char *argv[]);
141static void cleanup(void);
142
143static void test_user_cmd(const char *user_cmd);
144
145static int disable_protected_slinks;
146static int disable_protected_hlinks;
147
148/*
149 * changes links restrictions
150 * @param value can be:
151 * 0 - restrictions is off
152 * 1 - restrictions is on
153 */
154static void switch_protected_slinks(int value);
155static void switch_protected_hlinks(int value);
156
157static int get_protected_slinks(void);
158static int get_protected_hlinks(void);
159
160static void create_link_path(char *buffer, int size, const char *path);
161static int create_check_hlinks(const struct user_file *ufile, int owner);
162static int create_check_slinks(const struct user_file *ufile, int owner);
163static int check_symlink(const struct link_info *li);
164static int try_open(const char *name, int mode);
165/* try to open symlink in diff modes */
166static int try_symlink(const char *name);
167
168static int test_run(void);
169static void init_base_dirs(void);
170static void init_files_dirs(void);
171
172/* change effective user id and group id by name
173 * pass NULL to set root
174 */
175static void set_user(const char *name);
176
177/* add new created files to user struct */
178static void ufiles_add(int usr, char *path, int type);
179
180int main(int argc, char *argv[])
181{
182 setup(argc, argv);
183
184 test_run();
185
186 cleanup();
187
188 tst_exit();
189}
190
191static void setup(int argc, char *argv[])
192{
Cyril Hrubis0b9589f2014-05-27 17:40:33 +0200193 const char *msg;
Alexey Kodanev18f67672013-05-07 20:07:32 +0400194 msg = parse_opts(argc, argv, options, &help);
195 if (msg != NULL)
196 tst_brkm(TBROK, NULL, "OPTION PARSING ERROR - %s", msg);
197
198 tst_require_root(NULL);
199
DAN LI4140aa42013-05-16 14:08:37 +0800200 if (tst_kvercmp(3, 7, 0) < 0)
201 tst_brkm(TCONF, NULL,
Alexey Kodanev18f67672013-05-07 20:07:32 +0400202 "Test must be run with kernel 3.7 or newer");
Alexey Kodanev18f67672013-05-07 20:07:32 +0400203
204 /* initialize user names */
205 strcpy(users[ROOT].name, "root");
206
207 if (tmp_user_name == NULL)
208 tmp_user_name = default_user;
209 snprintf(users[TEST_USER].name, MAX_USER_NAME, "%s", tmp_user_name);
210
211 tst_sig(FORK, DEF_HANDLER, cleanup);
212
213 test_user_cmd("useradd");
214 /*
215 * enable hardlinks and symlinks restrictions,
216 * it's not defualt but have to check
217 */
218 if (!get_protected_hlinks()) {
219 switch_protected_hlinks(1);
220 disable_protected_hlinks = 1;
221 }
222 if (!get_protected_slinks()) {
223 switch_protected_slinks(1);
224 disable_protected_slinks = 1;
225 }
226
227 tst_tmpdir();
228
229 init_base_dirs();
230
231 init_files_dirs();
232}
233
234static int test_run(void)
235{
236 tst_resm(TINFO, " --- HARDLINKS AND SYMLINKS RESTRICTIONS TEST ---\n");
237
238 int result_slink = 0,
239 result_hlink = 0,
240 usr,
241 file;
242
243 const struct user_file *ufile;
244 /*
245 * create symlinks and hardlinks from each user's files
246 * to each world writable directory
247 */
248 for (usr = 0; usr < USERS_NUM; ++usr) {
249 /* get all users files and directories */
250 for (file = 0; file < users[usr].num; ++file) {
251 ufile = &users[usr].file[file];
252 result_slink |= create_check_slinks(ufile, usr);
253 result_hlink |= create_check_hlinks(ufile, usr);
254 }
255 }
256
257 /* final results */
258 tst_resm(TINFO, "All test-cases have been completed, summary:\n"
259 " - symlinks test:\t%s\n"
260 " - hardlinks test:\t%s",
261 (result_slink == 1) ? "FAIL" : "PASS",
262 (result_hlink == 1) ? "FAIL" : "PASS");
263
264 return result_slink | result_hlink;
265}
266
267static void cleanup(void)
268{
269 /* call cleanup function only once */
270 static int first_call = 1;
271 if (!first_call)
272 return;
273 first_call = 0;
274
275 set_user(NULL);
276
277 if (skip_cleanup)
278 return;
279
280 test_user_cmd("userdel -r");
281
282 if (disable_protected_hlinks) {
283 tst_resm(TINFO, "Disable protected hardlinks mode back");
284 switch_protected_hlinks(0);
285 }
286 if (disable_protected_slinks) {
287 tst_resm(TINFO, "Disable protected symlinks mode back");
288 switch_protected_slinks(0);
289 }
290
291 tst_rmdir();
Alexey Kodanev18f67672013-05-07 20:07:32 +0400292}
293
294static int get_protected_hlinks(void)
295{
296 int value = 0;
297 SAFE_FILE_SCANF(cleanup, hrdlink_proc_path, "%d", &value);
298 return value;
299}
300
301static int get_protected_slinks(void)
302{
303 int value = 0;
304 SAFE_FILE_SCANF(cleanup, symlink_proc_path, "%d", &value);
305 return value;
306}
307
308static void switch_protected_hlinks(int value)
309{
310 SAFE_FILE_PRINTF(cleanup, hrdlink_proc_path, "%d", value == 1);
311}
312
313static void switch_protected_slinks(int value)
314{
315 SAFE_FILE_PRINTF(cleanup, symlink_proc_path, "%d", value == 1);
316}
317
318static void test_user_cmd(const char *user_cmd)
319{
320 char cmd[MAX_CMD_LEN];
321 snprintf(cmd, MAX_CMD_LEN, "%s %s", user_cmd, users[TEST_USER].name);
322 if (system(cmd) != 0) {
323 tst_brkm(TBROK, cleanup, "Failed to run cmd: %s %s",
324 user_cmd, users[TEST_USER].name);
325 }
326}
327
328static void help(void)
329{
330 printf(" -s Skip cleanup.\n");
331 printf(" -u #user name : Define test user\n");
332}
333
334static void create_sub_dir(const char *path,
335 struct dir_params *bdir, mode_t mode)
336{
337 snprintf(bdir->path, MAX_PATH, "%s/tmp_%s",
338 path, users[bdir->owner].name);
339 SAFE_MKDIR(cleanup, bdir->path, mode);
340
341 if (bdir->sticky)
342 mode |= S_ISVTX;
343 chmod(bdir->path, mode);
344}
345
346static void init_base_dirs(void)
347{
Cyril Hrubis9c31ad22014-05-14 17:15:39 +0200348 char *cwd = tst_get_tmpdir();
Alexey Kodanev18f67672013-05-07 20:07:32 +0400349 cwd_offset = strlen(cwd);
350
351 mode_t mode = S_IRWXU | S_IRWXG | S_IRWXO;
352 chmod(cwd, mode);
353
354 strcpy(bdirs[0].path, cwd);
355 free(cwd);
356
357 bdirs[0].sticky = 0;
358 bdirs[0].world_writable = 1;
359
360 /* create subdir for each user */
361 int dir, usr;
362 for (usr = 0; usr < USERS_NUM; ++usr) {
363 set_user(users[usr].name);
364 dir = usr + 1;
365 bdirs[dir].sticky = 1;
366 bdirs[dir].world_writable = 1;
367 bdirs[dir].owner = usr;
368
369 create_sub_dir(bdirs[0].path, &bdirs[dir], mode);
370 }
371}
372
373static void init_files_dirs(void)
374{
375 int dir, usr;
376 /* create all other dirs and files */
377 for (dir = 0; dir < ARRAY_SIZE(bdirs); ++dir) {
378 for (usr = 0; usr < USERS_NUM; ++usr) {
379 set_user(users[usr].name);
380 char path[MAX_PATH];
381
382 /* create file in the main directory */
383 snprintf(path, MAX_PATH, "%s/%s%s",
384 bdirs[dir].path, users[usr].name, file_ext);
385 ufiles_add(usr, path, IS_FILE);
386
387 /* create file with S_IWOTH bit set */
388 strcat(path, "_w");
389 ufiles_add(usr, path, IS_FILE);
390
391 chmod(path, S_IRUSR | S_IRGRP | S_IWOTH | S_IROTH);
392
393 /* create sub directory */
394 snprintf(path, MAX_PATH, "%s/%s", bdirs[dir].path,
395 users[usr].name);
396 ufiles_add(usr, path, IS_DIRECTORY);
397
398 /* create local file inside sub directory */
399 snprintf(path + strlen(path), MAX_PATH - strlen(path),
400 "/local_%s%s", users[usr].name, file_ext);
401 ufiles_add(usr, path, IS_FILE);
402 }
403 }
404}
405
406static void ufiles_add(int usr, char *path, int type)
407{
408 int file = users[usr].num;
409
410 if (file >= MAX_ENTITIES)
411 tst_brkm(TBROK, cleanup, "Unexpected number of files");
412
413 struct user_file *ufile = &users[usr].file[file];
414
415 if (type == IS_FILE)
Stanislav Kholmanskikh90d19012013-11-06 12:23:07 +0400416 SAFE_TOUCH(cleanup, path, 0644, NULL);
Alexey Kodanev18f67672013-05-07 20:07:32 +0400417 else
418 SAFE_MKDIR(cleanup, path, 0755);
419
420 strcpy(ufile->path, path);
421
422 ufile->is_dir = (type == IS_DIRECTORY);
423 ++users[usr].num;
424}
425
426static void create_link_path(char *buffer, int size, const char *path)
427{
428 /* to make sure name is unique */
429 static int count;
430 ++count;
431
432 /* construct link name */
433 snprintf(buffer, size, "%s/link_%d", path, count);
434}
435
436static int create_check_slinks(const struct user_file *ufile, int owner)
437{
438 int result = 0;
439 int dir, usr;
440 for (dir = 0; dir < ARRAY_SIZE(bdirs); ++dir) {
441 for (usr = 0; usr < USERS_NUM; ++usr) {
442 /* set user who will create symlink */
443 set_user(users[usr].name);
444
445 struct link_info slink_info;
446 create_link_path(slink_info.path, MAX_PATH,
447 bdirs[dir].path);
448
449 slink_info.owner = usr;
450 slink_info.source_owner = owner;
451 slink_info.in_world_write = bdirs[dir].world_writable;
452 slink_info.in_sticky = bdirs[dir].sticky;
453 slink_info.dir_owner = bdirs[dir].owner;
454
455 if (symlink(ufile->path, slink_info.path) == -1) {
456 tst_brkm(TBROK, cleanup,
457 "Can't create symlink: %s",
458 slink_info.path);
459 }
460 result |= check_symlink(&slink_info);
461 }
462 }
463 return result;
464}
465
466static int create_check_hlinks(const struct user_file *ufile, int owner)
467{
468 int result = 0;
469 int dir, usr;
470 for (dir = 0; dir < ARRAY_SIZE(bdirs); ++dir) {
471 for (usr = 0; usr < USERS_NUM; ++usr) {
472 /* can't create hardlink to directory */
473 if (ufile->is_dir)
474 continue;
475 /* set user who will create hardlink */
476 set_user(users[usr].name);
477
478 struct link_info hlink_info;
479 create_link_path(hlink_info.path, MAX_PATH,
480 bdirs[dir].path);
481
482 int can_write = try_open(ufile->path, O_WRONLY) == 0;
483
484 int tst_flag = (can_write || usr == owner ||
485 usr == ROOT) ? CAN_CREATE : CANNOT_CREATE;
486
487 int fail;
488 fail = tst_flag != link(ufile->path, hlink_info.path);
489
490 result |= fail;
491 tst_resm((fail) ? TFAIL : TPASS,
492 "Expect: %s create hardlink '...%s' to '...%s', "
493 "owner '%s', curr.user '%s', w(%d)",
494 (tst_flag == CAN_CREATE) ? "can" : "can't",
495 ufile->path + cwd_offset,
496 hlink_info.path + cwd_offset,
497 users[owner].name, users[usr].name,
498 can_write);
499 }
500 }
501 return result;
502}
503
504static int check_symlink(const struct link_info *li)
505{
506 int symlink_result = 0;
507 int usr;
508 for (usr = 0; usr < USERS_NUM; ++usr) {
509 set_user(users[usr].name);
510 int tst_flag = (usr == li->dir_owner &&
511 li->in_world_write && li->in_sticky &&
512 usr != li->owner) ? CANNOT_FOLLOW : CAN_FOLLOW;
513
514 int fail = tst_flag != try_symlink(li->path);
515
516 symlink_result |= fail;
517
518 tst_resm((fail) ? TFAIL : TPASS,
519 "Expect: %s follow symlink '...%s', "
520 "owner '%s', src.owner '%s', "
521 "curr.user '%s', dir.owner '%s'",
522 (tst_flag == CAN_FOLLOW) ? "can" : "can't",
523 li->path + cwd_offset, users[li->owner].name,
524 users[li->source_owner].name, users[usr].name,
525 users[li->dir_owner].name);
526 }
527 return symlink_result;
528}
529
530/* differenet modes to try in the test */
531static const int o_modes[] = {
532 O_RDONLY,
533 O_WRONLY,
534 O_RDWR,
535 O_RDONLY | O_NONBLOCK | O_DIRECTORY,
536};
537
538static int try_symlink(const char *name)
539{
540 int mode;
541 for (mode = 0; mode < ARRAY_SIZE(o_modes); ++mode) {
542 if (try_open(name, o_modes[mode]) != -1)
543 return CAN_FOLLOW;
544 }
545
546 return CANNOT_FOLLOW;
547}
548
549static int try_open(const char *name, int mode)
550{
551 int fd = open(name, mode);
552
553 if (fd == -1)
554 return fd;
555
556 if (close(fd) == -1)
557 tst_brkm(TBROK, cleanup, "Can't close file: %s", name);
558
559 return 0;
560}
561
562static void set_user(const char *name)
563{
564 uid_t user_id = 0;
565 gid_t user_gr = 0;
566
567 if (name != NULL) {
568 struct passwd *pswd = getpwnam(name);
569
570 if (pswd == 0) {
571 tst_brkm(TBROK, cleanup,
572 "Failed to find user '%s'", name);
573 }
574 user_id = pswd->pw_uid;
575 user_gr = pswd->pw_gid;
576 }
577
578 SAFE_SETEGID(cleanup, user_gr);
579 SAFE_SETEUID(cleanup, user_id);
580}