• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Copyright (c) International Business Machines  Corp., 2002
4  * Copyright (c) 2021 SUSE LLC <mdoucha@suse.cz>
5  */
6 /*\
7  * [Description]
8  *
9  * Verify that the group ID and setgid bit are set correctly when a new file
10  * is created.
11  */
12 
13 #include <stdlib.h>
14 #include <sys/types.h>
15 #include <pwd.h>
16 #include "tst_test.h"
17 #include "tst_uid.h"
18 
19 #define MODE_RWX        0777
20 #define MODE_SGID       (S_ISGID|0777)
21 
22 #define DIR_A		"dir_a"
23 #define DIR_B		"dir_b"
24 #define SETGID_A	DIR_A "/setgid"
25 #define NOSETGID_A	DIR_A "/nosetgid"
26 #define SETGID_B	DIR_B "/setgid"
27 #define NOSETGID_B	DIR_B "/nosetgid"
28 #define ROOT_SETGID	DIR_B "/root_setgid"
29 
30 static char *tmpdir;
31 static uid_t orig_uid, nobody_uid;
32 static gid_t nobody_gid, free_gid;
33 static int fd = -1;
34 
setup(void)35 static void setup(void)
36 {
37 	struct passwd *ltpuser = SAFE_GETPWNAM("nobody");
38 
39 	orig_uid = getuid();
40 	nobody_uid = ltpuser->pw_uid;
41 	nobody_gid = ltpuser->pw_gid;
42 	tst_res(TINFO, "User nobody: uid = %d, gid = %d", (int)nobody_uid,
43 		(int)nobody_gid);
44 	free_gid = tst_get_free_gid(nobody_gid);
45 	tmpdir = tst_get_tmpdir();
46 }
47 
file_test(const char * name,mode_t mode,int sgid,gid_t gid)48 static void file_test(const char *name, mode_t mode, int sgid, gid_t gid)
49 {
50 	struct stat buf;
51 
52 	fd = SAFE_OPEN(name, O_CREAT | O_EXCL | O_RDWR, mode);
53 	SAFE_CLOSE(fd);
54 	SAFE_STAT(name, &buf);
55 
56 	if (buf.st_gid != gid) {
57 		tst_res(TFAIL, "%s: Incorrect group, %u != %u", name,
58 			buf.st_gid, gid);
59 	} else {
60 		tst_res(TPASS, "%s: Owned by correct group", name);
61 	}
62 
63 	if (sgid < 0) {
64 		tst_res(TINFO, "%s: Skipping setgid bit check", name);
65 		return;
66 	}
67 
68 	if (buf.st_mode & S_ISGID)
69 		tst_res(sgid ? TPASS : TFAIL, "%s: Setgid bit is set", name);
70 	else
71 		tst_res(sgid ? TFAIL : TPASS, "%s: Setgid bit not set", name);
72 }
73 
run(void)74 static void run(void)
75 {
76 	struct stat buf;
77 
78 	/* Create directories and set permissions */
79 	SAFE_MKDIR(DIR_A, MODE_RWX);
80 	SAFE_CHOWN(DIR_A, nobody_uid, free_gid);
81 	SAFE_STAT(DIR_A, &buf);
82 
83 	if (buf.st_mode & S_ISGID)
84 		tst_brk(TBROK, "%s: Setgid bit is set", DIR_A);
85 
86 	if (buf.st_gid != free_gid) {
87 		tst_brk(TBROK, "%s: Incorrect group, %u != %u", DIR_A,
88 			buf.st_gid, free_gid);
89 	}
90 
91 	SAFE_MKDIR(DIR_B, MODE_RWX);
92 	SAFE_CHOWN(DIR_B, nobody_uid, free_gid);
93 	SAFE_CHMOD(DIR_B, MODE_SGID);
94 	SAFE_STAT(DIR_B, &buf);
95 
96 	if (!(buf.st_mode & S_ISGID))
97 		tst_brk(TBROK, "%s: Setgid bit not set", DIR_B);
98 
99 	if (buf.st_gid != free_gid) {
100 		tst_brk(TBROK, "%s: Incorrect group, %u != %u", DIR_B,
101 			buf.st_gid, free_gid);
102 	}
103 
104 	/* Switch to user nobody and create two files in DIR_A */
105 	/* Both files should inherit GID from the process */
106 	SAFE_SETGID(nobody_gid);
107 	SAFE_SETREUID(-1, nobody_uid);
108 	file_test(NOSETGID_A, MODE_RWX, 0, nobody_gid);
109 	file_test(SETGID_A, MODE_SGID, 1, nobody_gid);
110 
111 	/* Create two files in DIR_B and validate owner and permissions */
112 	/* Both files should inherit GID from the parent directory */
113 	file_test(NOSETGID_B, MODE_RWX, 0, free_gid);
114 	/*
115 	 * CVE 2018-13405 (privilege escalation using setgid bit) has its
116 	 * own test, skip setgid check here
117 	 */
118 	file_test(SETGID_B, MODE_SGID, -1, free_gid);
119 
120 	/* Switch back to root UID and create a file in DIR_B */
121 	/* The file should inherid GID from parent directory */
122 	SAFE_SETREUID(-1, orig_uid);
123 	file_test(ROOT_SETGID, MODE_SGID, 1, free_gid);
124 
125 	/* Cleanup between loops */
126 	tst_purge_dir(tmpdir);
127 }
128 
cleanup(void)129 static void cleanup(void)
130 {
131 	if (fd >= 0)
132 		SAFE_CLOSE(fd);
133 
134 	free(tmpdir);
135 }
136 
137 static struct tst_test test = {
138 	.test_all = run,
139 	.setup = setup,
140 	.cleanup = cleanup,
141 	.needs_root = 1,
142 	.needs_tmpdir = 1,
143 };
144