2 * Marc E. Fiuczynski <mef@cs.princeton.edu>
4 * Copyright (c) 2004 The Trustees of Princeton University (Trustees).
6 * vsh is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2, or (at your option)
11 * vsh is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
14 * License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with Poptop; see the file COPYING. If not, write to the Free
18 * Software Foundation, 59 Temple Place - Suite 330, Boston, MA
35 #include <sys/syscall.h>
36 #include <asm/unistd.h>
37 #include <sys/mount.h>
38 #include <sys/types.h>
44 //--------------------------------------------------------------------
47 #undef CONFIG_VSERVER_LEGACY
49 /* Null byte made explicit */
50 #define NULLBYTE_SIZE 1
52 /* Base for all vserver roots for chroot */
53 #define VSERVER_ROOT_BASE "/vservers"
56 _PERROR(const char *format, char *file, int line, int _errno, ...)
61 fprintf(stderr, "%s:%d: ", file, line);
62 vfprintf(stderr, format, ap);
64 fprintf(stderr, ": %s (%d)", strerror(_errno), _errno);
71 #define PERROR(format, args...) _PERROR(format, __FILE__, __LINE__, errno, ## args)
73 /* Change to root:root (before entering new context) */
74 static int setuidgid_root()
87 static void compute_new_root(char *base, char **root, uid_t uid)
92 if ((pwd = getpwuid(uid)) == NULL) {
93 PERROR("getpwuid(%d)", uid);
98 strlen(base) + strlen("/") +
99 strlen(pwd->pw_name) + NULLBYTE_SIZE;
100 (*root) = (char *)malloc(root_len);
101 if ((*root) == NULL) {
102 PERROR("malloc(%d)", root_len);
106 sprintf((*root), "%s/%s", base, pwd->pw_name);
107 (*root)[root_len - 1] = '\0';
110 /* Example: sandbox_root = /vservers/bnc, relpath = /proc/1 */
111 static int sandbox_file_exists(char *sandbox_root, char *relpath)
113 struct stat stat_buf;
117 len = strlen(sandbox_root) + strlen(relpath) + NULLBYTE_SIZE;
118 if ((file = (char *)malloc(len)) == NULL) {
119 PERROR("malloc(%d)", len);
122 sprintf(file, "%s%s", sandbox_root, relpath);
123 file[len - 1] = '\0';
124 if (stat(file, &stat_buf) == 0) {
133 static int proc_mounted(char *sandbox_root)
135 return sandbox_file_exists(sandbox_root, "/proc/1");
138 static int devpts_mounted(char *sandbox_root)
140 return sandbox_file_exists(sandbox_root, "/dev/pts/0");
143 static void mount_proc(char *sandbox_root)
145 char *source = "/proc";
149 len = strlen(sandbox_root) + strlen("/") + strlen("proc") + NULLBYTE_SIZE;
150 if ((target = (char *)malloc(len)) == NULL) {
151 PERROR("malloc(%d)", len);
155 sprintf(target, "%s/proc", sandbox_root);
156 target[len - 1] = '\0';
157 if (!proc_mounted(sandbox_root))
158 mount(source, target, "proc", MS_BIND | MS_RDONLY, NULL);
163 static void mount_devpts(char *sandbox_root)
165 char *source = "/dev/pts";
169 len = strlen(sandbox_root) + strlen("/") + strlen("dev/pts") + NULLBYTE_SIZE;
170 if ((target = (char *)malloc(len)) == NULL) {
171 PERROR("malloc(%d)", len);
175 sprintf(target, "%s/dev/pts", sandbox_root);
176 target[len - 1] = '\0';
177 if (!devpts_mounted(sandbox_root))
178 mount(source, target, "devpts", 0, NULL);
183 static int sandbox_chroot(uid_t uid)
185 char *sandbox_root = NULL;
187 compute_new_root(VSERVER_ROOT_BASE,&sandbox_root, uid);
188 mount_proc(sandbox_root);
189 mount_devpts(sandbox_root);
190 if (chroot(sandbox_root) < 0) {
191 PERROR("chroot(%s)", sandbox_root);
194 if (chdir("/") < 0) {
201 #define WHITESPACE(buffer,index,len) \
202 while(isspace((int)buffer[index])) \
203 if (index < len) index++; else goto out;
210 #define VSERVERCONF "/etc/vservers/"
211 static void get_limits(char *context, int *cpu, int *mem, int *task) {
213 size_t len = strlen(VSERVERCONF) + strlen(context) + strlen(".conf") + NULLBYTE_SIZE;
214 char *conf = (char *)malloc(len);
215 struct resources list[] =
222 sprintf(conf, "%s%s.conf", VSERVERCONF, context);
224 /* open the conf file for reading */
225 fb = fopen(conf,"r");
228 char *buffer = malloc(1000);
231 /* the conf file exist */
232 while((p=fgets(buffer,1000-1,fb))!=NULL) {
234 len = strnlen(buffer,1000);
235 WHITESPACE(buffer,index,len);
236 if (buffer[index] == '#')
239 for (r=list; r->name; r++)
240 if ((p=strstr(&buffer[index],r->name))!=NULL) {
241 /* adjust index into buffer */
242 index+= (p-&buffer[index])+strlen(r->name);
244 /* skip over whitespace */
245 WHITESPACE(buffer,index,len);
247 /* expecting to see = sign */
248 if (buffer[index++]!='=') goto out;
250 /* skip over whitespace */
251 WHITESPACE(buffer,index,len);
253 /* expecting to see a digit for number */
254 if (!isdigit((int)buffer[index])) goto out;
256 *r->limit = atoi(&buffer[index]);
263 fprintf(stderr,"cannot open %s\n",conf);
269 static int sandbox_processes(xid_t xid, char *context)
271 #ifdef CONFIG_VSERVER_LEGACY
275 flags |= 1; /* VX_INFO_LOCK -- cannot request a new vx_id */
276 /* flags |= 4; VX_INFO_NPROC -- limit number of procs in a context */
278 (void) vc_new_s_context(xid, 0, flags);
280 /* use legacy dirty hack for capremove */
281 if (vc_new_s_context(VC_SAMECTX, vc_get_insecurebcaps(), flags) == VC_NOCTX) {
282 PERROR("vc_new_s_context(%u, 0x%16ullx, 0x%08x)",
283 VC_SAMECTX, vc_get_insecurebcaps(), flags);
287 struct vc_ctx_caps caps;
288 struct vc_ctx_flags flags;
289 int cpu = VC_LIM_KEEP;
290 int mem = VC_LIM_KEEP;
291 int task = VC_LIM_KEEP;
292 get_limits(context,&cpu, &mem, &task);
293 (void) (sandbox_chroot(xid));
295 caps.ccaps = ~vc_get_insecureccaps();
297 caps.bcaps = ~vc_get_insecurebcaps();
300 flags.flagword = VC_VXF_INFO_LOCK;
301 flags.mask = VC_VXF_STATE_SETUP | VC_VXF_INFO_LOCK;
304 if ((vc_ctx_create(xid) == VC_NOCTX) && (errno != EEXIST)) {
305 PERROR("vc_ctx_create(%d)", xid);
309 if (errno != EEXIST) {
310 struct vc_rlimit limits;
311 /* The context did not exist before, which requires that we set the various limit */
317 limits.min = VC_LIM_KEEP;
318 limits.soft = VC_LIM_KEEP;
320 if (vc_set_rlimit(xid, 5, &limits)) {
324 limits.min = VC_LIM_KEEP;
325 limits.soft = VC_LIM_KEEP;
327 if (vc_set_rlimit(xid, 6, &limits)) {
328 /* setting limit failed */
332 if (vc_set_ccaps(xid, &caps) == -1) {
333 PERROR("vc_set_ccaps(%d, 0x%16ullx/0x%16ullx, 0x%16ullx/0x%16ullx)\n",
334 xid, caps.ccaps, caps.cmask, caps.bcaps, caps.bmask);
338 if (vc_set_cflags(xid, &flags) == -1) {
339 PERROR("vc_set_cflags(%d, 0x%16llx/0x%16llx)\n",
340 xid, flags.flagword, flags.mask);
348 void runas_slice_user(char *username)
350 struct passwd pwdd, *pwd = &pwdd, *result;
352 char *home_env, *logname_env, *mail_env, *shell_env, *user_env;
353 int home_len, logname_len, mail_len, shell_len, user_len;
355 static char *envp[10];
358 pwdBuffer_len = sysconf(_SC_GETPW_R_SIZE_MAX);
359 if (pwdBuffer_len == -1) {
360 PERROR("sysconf(_SC_GETPW_R_SIZE_MAX)");
364 pwdBuffer = (char*)malloc(pwdBuffer_len);
365 if (pwdBuffer == NULL) {
366 PERROR("malloc(%d)", pwdBuffer_len);
371 if ((getpwnam_r(username,pwd,pwdBuffer,pwdBuffer_len, &result) != 0) || (errno != 0)) {
372 PERROR("getpwnam_r(%s)", username);
376 if (setgid(pwd->pw_gid) < 0) {
377 PERROR("setgid(%d)", pwd->pw_gid);
381 if (setuid(pwd->pw_uid) < 0) {
382 PERROR("setuid(%d)", pwd->pw_uid);
386 if (chdir(pwd->pw_dir) < 0) {
387 PERROR("chdir(%s)", pwd->pw_dir);
391 home_len = strlen("HOME=") + strlen(pwd->pw_dir) + NULLBYTE_SIZE;
392 logname_len = strlen("LOGNAME=") + strlen(username) + NULLBYTE_SIZE;
393 mail_len = strlen("MAIL=/var/spool/mail/") + strlen(username)
395 shell_len = strlen("SHELL=") + strlen(pwd->pw_shell) + NULLBYTE_SIZE;
396 user_len = strlen("USER=") + strlen(username) + NULLBYTE_SIZE;
398 home_env = (char *)malloc(home_len);
399 logname_env = (char *)malloc(logname_len);
400 mail_env = (char *)malloc(mail_len);
401 shell_env = (char *)malloc(shell_len);
402 user_env = (char *)malloc(user_len);
404 if ((home_env == NULL) ||
405 (logname_env == NULL) ||
406 (mail_env == NULL) ||
407 (shell_env == NULL) ||
408 (user_env == NULL)) {
413 sprintf(home_env, "HOME=%s", pwd->pw_dir);
414 sprintf(logname_env, "LOGNAME=%s", username);
415 sprintf(mail_env, "MAIL=/var/spool/mail/%s", username);
416 sprintf(shell_env, "SHELL=%s", pwd->pw_shell);
417 sprintf(user_env, "USER=%s", username);
419 home_env[home_len - 1] = '\0';
420 logname_env[logname_len - 1] = '\0';
421 mail_env[mail_len - 1] = '\0';
422 shell_env[shell_len - 1] = '\0';
423 user_env[user_len - 1] = '\0';
426 envp[1] = logname_env;
432 if ((putenv(home_env) < 0) ||
433 (putenv(logname_env) < 0) ||
434 (putenv(mail_env) < 0) ||
435 (putenv(shell_env) < 0) ||
436 (putenv(user_env) < 0)) {
437 PERROR("vserver: putenv error ");
442 void slice_enter(char *context)
444 struct passwd pwdd, *pwd = &pwdd, *result;
449 pwdBuffer_len = sysconf(_SC_GETPW_R_SIZE_MAX);
450 if (pwdBuffer_len == -1) {
451 PERROR("sysconf(_SC_GETPW_R_SIZE_MAX)");
455 pwdBuffer = (char*)malloc(pwdBuffer_len);
456 if (pwdBuffer == NULL) {
457 PERROR("malloc(%d)", pwdBuffer_len);
462 if ((getpwnam_r(context,pwd,pwdBuffer,pwdBuffer_len, &result) != 0) || (errno != 0)) {
463 PERROR("getpwnam_r(%s)", context);
468 if (setuidgid_root() < 0) { /* For chroot, new_s_context */
469 fprintf(stderr, "vsh: Could not become root, check that SUID flag is set on binary\n");
473 #ifdef CONFIG_VSERVER_LEGACY
474 (void) (sandbox_chroot(uid));
477 if (sandbox_processes((xid_t) uid, context) < 0) {
478 fprintf(stderr, "vsh: Could not change context to %d\n", uid);
483 //--------------------------------------------------------------------
485 #define DEFAULT_SHELL "/bin/sh"
487 /* Exit statuses for programs like 'env' that exec other programs.
488 EXIT_FAILURE might not be 1, so use EXIT_FAIL in such programs. */
491 EXIT_CANNOT_INVOKE = 126,
495 int main(int argc, char **argv)
497 struct passwd pwdd, *pwd = &pwdd, *result;
498 char *context, *username, *shell, *pwdBuffer;
509 if ((pwd = getpwuid(uid)) == NULL) {
510 PERROR("getpwuid(%d)", uid);
514 context = (char*)strdup(pwd->pw_name);
520 /* enter vserver "context" */
521 slice_enter(context);
523 /* Now run as username in this context. Note that for PlanetLab's
524 vserver configuration the context name also happens to be the
525 "default" username within the vserver context.
528 runas_slice_user(username);
530 /* With the uid/gid appropriately set. Let's figure out what the
531 * shell in the vserver's /etc/passwd is for the given username.
534 pwdBuffer_len = sysconf(_SC_GETPW_R_SIZE_MAX);
535 if (pwdBuffer_len == -1) {
536 PERROR("sysconf(_SC_GETPW_R_SIZE_MAX");
539 pwdBuffer = (char*)malloc(pwdBuffer_len);
540 if (pwdBuffer == NULL) {
541 PERROR("malloc(%d)", pwdBuffer_len);
546 if ((getpwnam_r(username,pwd,pwdBuffer,pwdBuffer_len, &result) != 0) || (errno != 0)) {
547 PERROR("getpwnam_r(%s)", username);
551 /* Make sure pw->pw_shell is non-NULL.*/
552 if (pwd->pw_shell == NULL || pwd->pw_shell[0] == '\0') {
553 pwd->pw_shell = (char *) DEFAULT_SHELL;
556 shell = (char *)strdup(pwd->pw_shell);
562 /* Check whether 'su' or 'sshd' invoked us as a login shell or
563 not; did this above when testing argv[0]=='-'.
568 args = (char**)malloc(sizeof(char*)*(argc+2));
570 PERROR("malloc(%d)", sizeof(char*)*(argc+2));
575 for(i=1;i<argc+1;i++) {
580 (void) execvp(shell,argv);
582 int exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE);
586 return 0; /* shutup compiler */