|
17 | 17 | #include <stdbool.h> |
18 | 18 | #include <unistd.h> |
19 | 19 | #include <string.h> |
| 20 | +#include <fcntl.h> |
| 21 | +#include <sched.h> |
| 22 | +#include <sys/mount.h> |
20 | 23 | #include <error.h> |
21 | 24 |
|
22 | 25 | #include "supercall.h" |
|
47 | 50 | #define PROGRAM_NAME "su" |
48 | 51 |
|
49 | 52 | static void run_shell(char const *, char const *, char **, size_t); |
50 | | -/* If true, change some environment vars to indicate the user su'd to. */ |
51 | | -static bool change_environment; |
52 | | - |
53 | 53 | extern const char program_name[]; |
54 | 54 | extern const char *key; |
55 | 55 |
|
| 56 | +int setns(int __fd, int __ns_type); |
| 57 | +int unshare(int __flags); |
| 58 | + |
56 | 59 | char *last_component(char const *name) |
57 | 60 | { |
58 | 61 | char const *base = name; |
@@ -86,14 +89,39 @@ static void xsetenv(char const *name, char const *val) |
86 | 89 | putenv(string); |
87 | 90 | } |
88 | 91 |
|
89 | | -/* Become the user and group(s) specified by PW. */ |
90 | | -static void change_identity(const struct passwd *pw) |
| 92 | +static int switch_mnt_ns(int pid) |
| 93 | +{ |
| 94 | + int rc = 0; |
| 95 | + char mnt[32]; |
| 96 | + snprintf(mnt, sizeof(mnt), "/proc/%d/ns/mnt", pid); |
| 97 | + if ((rc = access(mnt, R_OK)) < 0) { |
| 98 | + error(0, errno, "access %s error\n", mnt); |
| 99 | + return rc; |
| 100 | + } |
| 101 | + int fd = open(mnt, O_RDONLY); |
| 102 | + if (fd < 0) { |
| 103 | + error(0, errno, "access %s\n", mnt); |
| 104 | + rc = fd; |
| 105 | + return rc; |
| 106 | + } |
| 107 | + // switch to its namespace |
| 108 | + if ((rc = setns(fd, 0)) < 0) error(0, errno, "setns %d error\n", fd); |
| 109 | + close(fd); |
| 110 | + |
| 111 | + return rc; |
| 112 | +} |
| 113 | + |
| 114 | +static void set_identity(uid_t uid, gid_t *gids, int gids_num) |
91 | 115 | { |
92 | | - errno = 0; |
93 | | - if (initgroups(pw->pw_name, pw->pw_gid) == -1) error(EXIT_CANCELED, errno, "cannot set groups"); |
94 | | - endgrent(); |
95 | | - if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid)) error(EXIT_CANCELED, errno, "cannot set group id"); |
96 | | - if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) error(EXIT_CANCELED, errno, "cannot set user id"); |
| 116 | + gid_t gid; |
| 117 | + if (gids_num > 0) { |
| 118 | + if (setgroups(gids_num, gids)) error(EXIT_CANCELED, errno, "cannot set groups"); |
| 119 | + gid = gids[0]; |
| 120 | + } else { |
| 121 | + gid = uid; |
| 122 | + } |
| 123 | + if (setresgid(gid, gid, gid)) error(EXIT_CANCELED, errno, "cannot set gids"); |
| 124 | + if (setresuid(uid, uid, uid)) error(EXIT_CANCELED, errno, "cannot set uids"); |
97 | 125 | } |
98 | 126 |
|
99 | 127 | static void __attribute__((noreturn)) |
@@ -130,104 +158,165 @@ static void usage(int status) |
130 | 158 | fprintf(stdout, |
131 | 159 | "-h, --help Print this help message. \n" |
132 | 160 | "-c, --command=COMMAND pass a single COMMAND to the shell with -c\n" |
133 | | - "-m, --preserve-environment do not reset environment variables\n" |
134 | | - "-p same as -m\n" |
135 | | - "-s, --shell=SHELL use SHELL instead of the default\n" |
136 | | - "-x, --scontext=SCONTEXT Switch security context to SCONTEXT, If SCONTEXT is not specified\n" |
| 161 | + "-m, -p, --preserve-environment do not reset environment variables\n" |
| 162 | + "-g, --group GROUP Specify the primary group\n" |
| 163 | + "-G, --supp-group GROUP Specify a supplementary group.\n" |
| 164 | + " The first specified supplementary group is also used\n" |
| 165 | + " as a primary group if the option -g is not specified.\n" |
| 166 | + "-t, --target PID PID to take mount namespace from\n " |
| 167 | + "-s, --shell SHELL use SHELL instead of the default\n" |
| 168 | + "-, -l, --login Pretend the shell to be a login shell\n" |
| 169 | + "-Z, -x, --context SCONTEXT Switch security context to SCONTEXT, If SCONTEXT is not specified\n" |
137 | 170 | " or specified with a non-existent value, bypass all selinux permission\n" |
138 | 171 | " checks for all calls initiated by this task using hooks, \n" |
139 | 172 | " but the permission determined by other task remain unchanged. \n" |
| 173 | + "-M, --mount-master force run in the global mount namespace\n" |
140 | 174 | ""); |
141 | 175 | } |
142 | 176 | exit(status); |
143 | 177 | } |
144 | 178 |
|
145 | | -static struct option const longopts[] = { |
146 | | - { "command", required_argument, NULL, 'c' }, { "preserve-environment", no_argument, NULL, 'p' }, |
147 | | - { "shell", required_argument, NULL, 's' }, { "scontext", required_argument, NULL, 'x' }, |
148 | | - { "--help", no_argument, NULL, 'h' }, { NULL, 0, NULL, 0 } |
149 | | -}; |
| 179 | +static struct option const longopts[] = { { "command", required_argument, 0, 'c' }, |
| 180 | + { "help", no_argument, 0, 'h' }, |
| 181 | + { "login", no_argument, 0, 'l' }, |
| 182 | + { "preserve-environment", no_argument, 0, 'p' }, |
| 183 | + { "shell", required_argument, 0, 's' }, |
| 184 | + { "version", no_argument, 0, 'v' }, |
| 185 | + { "context", required_argument, 0, 'Z' }, |
| 186 | + { "mount-master", no_argument, 0, 'M' }, |
| 187 | + { "target", required_argument, 0, 't' }, |
| 188 | + { "group", required_argument, 0, 'g' }, |
| 189 | + { "supp-group", required_argument, 0, 'G' }, |
| 190 | + { 0, 0, 0, 0 }, |
| 191 | + { NULL, 0, NULL, 0 } }; |
| 192 | + |
| 193 | +uid_t uid = 0; |
| 194 | +bool login = false; |
| 195 | +bool keepenv = false; |
| 196 | +bool isolated = false; |
| 197 | +pid_t target = -1; |
| 198 | + |
| 199 | +char *command = NULL; |
| 200 | +char *shell = NULL; |
| 201 | +char *scontext = NULL; |
| 202 | + |
| 203 | +gid_t gids_num = 0; |
| 204 | +gid_t gids[128] = { -1 }; |
| 205 | + |
| 206 | +const char *new_user = DEFAULT_USER; |
150 | 207 |
|
151 | 208 | int su_main(int argc, char **argv) |
152 | 209 | { |
153 | | - int optc; |
154 | | - const char *new_user = DEFAULT_USER; |
155 | | - char *command = NULL; |
156 | | - char *shell = NULL; |
157 | | - char *scontext = NULL; |
| 210 | + int optc, c; |
| 211 | + |
158 | 212 | struct passwd *pw; |
159 | 213 | struct passwd pw_copy; |
160 | 214 |
|
161 | | - change_environment = true; |
| 215 | + pid_t origin_pid = getpid(); |
162 | 216 |
|
163 | | - while ((optc = getopt_long(argc, argv, "c:flmps:x:h", longopts, NULL)) != -1) { |
164 | | - switch (optc) { |
| 217 | + while ((c = getopt_long(argc, argv, "c:hlmps:VvuZ:Mt:g:G:", longopts, 0)) != -1) { |
| 218 | + switch (c) { |
165 | 219 | case 'c': |
166 | 220 | command = optarg; |
167 | 221 | break; |
| 222 | + case 'h': |
| 223 | + usage(EXIT_SUCCESS); |
| 224 | + case 'l': |
| 225 | + login = true; |
| 226 | + break; |
168 | 227 | case 'm': |
169 | 228 | case 'p': |
170 | | - change_environment = false; |
| 229 | + keepenv = true; |
171 | 230 | break; |
172 | 231 | case 's': |
173 | 232 | shell = optarg; |
174 | 233 | break; |
175 | | - case 'x': |
| 234 | + case 'Z': |
176 | 235 | scontext = optarg; |
177 | 236 | break; |
178 | | - case 'h': |
179 | | - usage(EXIT_SUCCESS); |
| 237 | + case 'M': |
| 238 | + case 't': |
| 239 | + if (target != -1) { |
| 240 | + error(-EINVAL, 0, "Can't use -M and -t at the same time\n"); |
| 241 | + } |
| 242 | + if (optarg == 0) { |
| 243 | + target = 0; |
| 244 | + } else { |
| 245 | + target = atol(optarg); |
| 246 | + if (*optarg == '-' || target == -1) { |
| 247 | + error(-EINVAL, 0, "Invalid PID: %s\n", optarg); |
| 248 | + } |
| 249 | + } |
| 250 | + break; |
| 251 | + case 'g': |
| 252 | + case 'G': |
| 253 | + if (atol(optarg) >= 0) { |
| 254 | + if (gids_num >= sizeof(gids) / sizeof(gids[0])) break; |
| 255 | + gids[gids_num++] = atol(optarg); |
| 256 | + } else { |
| 257 | + error(-EINVAL, 0, "Invalid GID: %s\n", optarg); |
| 258 | + } |
| 259 | + break; |
180 | 260 | default: |
181 | 261 | usage(EXIT_FAILURE); |
182 | 262 | } |
183 | 263 | } |
184 | 264 |
|
| 265 | + // login |
| 266 | + if (optind < argc && strcmp(argv[optind], "-") == 0) { |
| 267 | + login = true; |
| 268 | + optind++; |
| 269 | + } |
| 270 | + |
| 271 | + // user uid |
185 | 272 | if (optind < argc) new_user = argv[optind++]; |
186 | 273 |
|
187 | | - // |
| 274 | + pw = getpwnam(new_user); |
| 275 | + if (pw) |
| 276 | + uid = pw->pw_uid; |
| 277 | + else |
| 278 | + uid = atol(new_user); |
| 279 | + optind++; |
| 280 | + |
| 281 | + // environment |
| 282 | + if (!shell && keepenv) shell = getenv("SHELL"); |
| 283 | + if (!shell) shell = DEFAULT_SHELL; |
| 284 | + |
| 285 | + // su from kernel |
188 | 286 | struct su_profile profile = { 0 }; |
189 | 287 | profile.uid = getuid(); |
| 288 | + profile.to_uid = 0; |
190 | 289 | if (scontext) strncpy(profile.scontext, scontext, sizeof(profile.scontext) - 1); |
191 | 290 | if (sc_su(key, &profile)) error(-EACCES, 0, "incorrect super key"); |
192 | 291 |
|
193 | | - pw = getpwnam(new_user); |
194 | | - if (!(pw && pw->pw_name && pw->pw_name[0] && pw->pw_dir && pw->pw_dir[0])) |
195 | | - error(EXIT_CANCELED, 0, "user %s does not exist", new_user); |
196 | | - |
197 | | - pw_copy = *pw; |
198 | | - pw = &pw_copy; |
199 | | - pw->pw_name = strdup(pw->pw_name); |
200 | | - if (pw->pw_passwd) pw->pw_passwd = strdup(pw->pw_passwd); |
201 | | - pw->pw_dir = strdup(pw->pw_dir); |
202 | | - pw->pw_shell = strdup(pw->pw_shell && pw->pw_shell[0] ? pw->pw_shell : DEFAULT_SHELL); |
203 | | - endpwent(); |
204 | | - |
205 | | - if (!shell && !change_environment) shell = getenv("SHELL"); |
206 | | - shell = strdup(shell ? shell : pw->pw_shell); |
| 292 | + // session leader |
| 293 | + setsid(); |
| 294 | + |
| 295 | + // namespaces |
| 296 | + if (target > 0) { // namespace of pid |
| 297 | + if (switch_mnt_ns(target)) { |
| 298 | + error(0, errno, "switch_mnt_ns failed, fallback to global\n"); |
| 299 | + } else { |
| 300 | + if (isolated) { // new isolated namespace |
| 301 | + if (unshare(CLONE_NEWNS) < 0) error(0, errno, "unshare"); |
| 302 | + if (mount(0, "/", 0, MS_PRIVATE | MS_REC, 0) < 0) error(0, errno, "mount"); |
| 303 | + } |
| 304 | + } |
| 305 | + } |
207 | 306 |
|
208 | | - if (change_environment) { |
| 307 | + if (!keepenv) { |
209 | 308 | xsetenv("HOME", pw->pw_dir); |
210 | 309 | xsetenv("SHELL", shell); |
211 | | - // add path |
212 | | - // char *old_path = getenv("PATH"); |
213 | | - // char *add_path = pw->pw_uid ? DEFAULT_PATH : DEFAULT_ROOT_PATH; |
214 | | - // int path_len = strlen(old_path) + strlen(add_path) + 1; |
215 | | - // char *new_path = malloc(path_len); |
216 | | - // memset(new_path, 0, path_len); |
217 | | - // strcat(new_path, old_path); |
218 | | - // strcat(new_path, add_path); |
219 | | - // xsetenv("PATH", new_path); |
220 | | - // free(new_path); |
221 | 310 | xsetenv("PATH", pw->pw_uid ? DEFAULT_PATH : DEFAULT_ROOT_PATH); |
222 | 311 | if (pw->pw_uid) { |
223 | 312 | xsetenv("USER", pw->pw_name); |
224 | 313 | xsetenv("LOGNAME", pw->pw_name); |
225 | 314 | } |
226 | 315 | } |
227 | 316 |
|
228 | | - change_identity(pw); |
| 317 | + set_identity(uid, gids, gids_num); |
229 | 318 |
|
230 | | - if (chdir(pw->pw_dir) != 0) error(0, errno, "warning: cannot change directory to %s", pw->pw_dir); |
| 319 | + if (chdir(pw->pw_dir) != 0) error(0, errno, "cannot change directory: %s", pw->pw_dir); |
231 | 320 |
|
232 | 321 | if (ferror(stderr)) exit(EXIT_CANCELED); |
233 | 322 |
|
|
0 commit comments