Skip to content

Commit 0cf11f4

Browse files
author
bmax
committed
feat: su support more option
1 parent b08831c commit 0cf11f4

File tree

2 files changed

+149
-60
lines changed

2 files changed

+149
-60
lines changed

user/su.c

Lines changed: 148 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
#include <stdbool.h>
1818
#include <unistd.h>
1919
#include <string.h>
20+
#include <fcntl.h>
21+
#include <sched.h>
22+
#include <sys/mount.h>
2023
#include <error.h>
2124

2225
#include "supercall.h"
@@ -47,12 +50,12 @@ enum
4750
#define PROGRAM_NAME "su"
4851

4952
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-
5353
extern const char program_name[];
5454
extern const char *key;
5555

56+
int setns(int __fd, int __ns_type);
57+
int unshare(int __flags);
58+
5659
char *last_component(char const *name)
5760
{
5861
char const *base = name;
@@ -86,14 +89,39 @@ static void xsetenv(char const *name, char const *val)
8689
putenv(string);
8790
}
8891

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)
91115
{
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");
97125
}
98126

99127
static void __attribute__((noreturn))
@@ -130,104 +158,165 @@ static void usage(int status)
130158
fprintf(stdout,
131159
"-h, --help Print this help message. \n"
132160
"-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"
137170
" or specified with a non-existent value, bypass all selinux permission\n"
138171
" checks for all calls initiated by this task using hooks, \n"
139172
" but the permission determined by other task remain unchanged. \n"
173+
"-M, --mount-master force run in the global mount namespace\n"
140174
"");
141175
}
142176
exit(status);
143177
}
144178

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;
150207

151208
int su_main(int argc, char **argv)
152209
{
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+
158212
struct passwd *pw;
159213
struct passwd pw_copy;
160214

161-
change_environment = true;
215+
pid_t origin_pid = getpid();
162216

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) {
165219
case 'c':
166220
command = optarg;
167221
break;
222+
case 'h':
223+
usage(EXIT_SUCCESS);
224+
case 'l':
225+
login = true;
226+
break;
168227
case 'm':
169228
case 'p':
170-
change_environment = false;
229+
keepenv = true;
171230
break;
172231
case 's':
173232
shell = optarg;
174233
break;
175-
case 'x':
234+
case 'Z':
176235
scontext = optarg;
177236
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;
180260
default:
181261
usage(EXIT_FAILURE);
182262
}
183263
}
184264

265+
// login
266+
if (optind < argc && strcmp(argv[optind], "-") == 0) {
267+
login = true;
268+
optind++;
269+
}
270+
271+
// user uid
185272
if (optind < argc) new_user = argv[optind++];
186273

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
188286
struct su_profile profile = { 0 };
189287
profile.uid = getuid();
288+
profile.to_uid = 0;
190289
if (scontext) strncpy(profile.scontext, scontext, sizeof(profile.scontext) - 1);
191290
if (sc_su(key, &profile)) error(-EACCES, 0, "incorrect super key");
192291

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+
}
207306

208-
if (change_environment) {
307+
if (!keepenv) {
209308
xsetenv("HOME", pw->pw_dir);
210309
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);
221310
xsetenv("PATH", pw->pw_uid ? DEFAULT_PATH : DEFAULT_ROOT_PATH);
222311
if (pw->pw_uid) {
223312
xsetenv("USER", pw->pw_name);
224313
xsetenv("LOGNAME", pw->pw_name);
225314
}
226315
}
227316

228-
change_identity(pw);
317+
set_identity(uid, gids, gids_num);
229318

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);
231320

232321
if (ferror(stderr)) exit(EXIT_CANCELED);
233322

version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
#define MAJOR 0
22
#define MINOR 8
3-
#define PATCH 4
3+
#define PATCH 5

0 commit comments

Comments
 (0)