📄 pcontrol.c
字号:
/* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only. * See the file usr/src/LICENSING.NOTICE in this distribution or * http://www.opensolaris.org/license/ for details. */#pragma ident "@(#)Pcontrol.c 1.37 04/11/01 SMI"#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <ctype.h>#include <fcntl.h>#include <string.h>#include <memory.h>#include <errno.h>#include <dirent.h>#include <limits.h>#include <signal.h>#include <sys/types.h>#include <sys/uio.h>#include <sys/stat.h>#include <sys/resource.h>#include <sys/param.h>#include <sys/stack.h>#include <sys/fault.h>#include <sys/syscall.h>#include <sys/sysmacros.h>#include "libproc.h"#include "Pcontrol.h"#include "Putil.h"#include "P32ton.h"int _libproc_debug; /* set non-zero to enable debugging printfs */sigset_t blockable_sigs; /* signals to block when we need to be safe */static int minfd; /* minimum file descriptor returned by dupfd(fd, 0) *//* * Function prototypes for static routines in this module. */static void deadcheck(struct ps_prochandle *);static void restore_tracing_flags(struct ps_prochandle *);static void Lfree_internal(struct ps_prochandle *, struct ps_lwphandle *);/* * Read/write interface for live processes: just pread/pwrite the * /proc/<pid>/as file: */static ssize_tPread_live(struct ps_prochandle *P, void *buf, size_t n, uintptr_t addr){ return (pread(P->asfd, buf, n, (off_t)addr));}static ssize_tPwrite_live(struct ps_prochandle *P, const void *buf, size_t n, uintptr_t addr){ return (pwrite(P->asfd, buf, n, (off_t)addr));}static const ps_rwops_t P_live_ops = { Pread_live, Pwrite_live };/* * This is the library's .init handler. */#pragma init(_libproc_init)void_libproc_init(void){ _libproc_debug = getenv("LIBPROC_DEBUG") != NULL; (void) sigfillset(&blockable_sigs); (void) sigdelset(&blockable_sigs, SIGKILL); (void) sigdelset(&blockable_sigs, SIGSTOP);}/* * Call set_minfd() once before calling dupfd() several times. * We assume that the application will not reduce its current file * descriptor limit lower than 512 once it has set at least that value. */intset_minfd(void){ static mutex_t minfd_lock = DEFAULTMUTEX; struct rlimit rlim; int fd; if ((fd = minfd) < 256) { (void) mutex_lock(&minfd_lock); if ((fd = minfd) < 256) { if (getrlimit(RLIMIT_NOFILE, &rlim) != 0) rlim.rlim_cur = rlim.rlim_max = 0; if (rlim.rlim_cur >= 512) fd = 256; else if ((fd = rlim.rlim_cur / 2) < 3) fd = 3; minfd = fd; } (void) mutex_unlock(&minfd_lock); } return (fd);}intdupfd(int fd, int dfd){ int mfd; /* * Make fd be greater than 255 (the 32-bit stdio limit), * or at least make it greater than 2 so that the * program will work when spawned by init(1m). * Also, if dfd is non-zero, dup the fd to be dfd. */ if ((mfd = minfd) == 0) mfd = set_minfd(); if (dfd > 0 || (0 <= fd && fd < mfd)) { if (dfd <= 0) dfd = mfd; dfd = fcntl(fd, F_DUPFD, dfd); (void) close(fd); fd = dfd; } /* * Mark it close-on-exec so any created process doesn't inherit it. */ if (fd >= 0) (void) fcntl(fd, F_SETFD, FD_CLOEXEC); return (fd);}/* * Create a new controlled process. * Leave it stopped on successful exit from exec() or execve(). * Return an opaque pointer to its process control structure. * Return NULL if process cannot be created (fork()/exec() not successful). */struct ps_prochandle *Pxcreate(const char *file, /* executable file name */ char *const *argv, /* argument vector */ char *const *envp, /* environment */ int *perr, /* pointer to error return code */ char *path, /* if non-null, holds exec path name on return */ size_t len) /* size of the path buffer */{ char execpath[PATH_MAX]; char procname[100]; struct ps_prochandle *P; pid_t pid; int fd; char *fname; int rc; int lasterrno = 0; if (len == 0) /* zero length, no path */ path = NULL; if (path != NULL) *path = '\0'; if ((P = malloc(sizeof (struct ps_prochandle))) == NULL) { *perr = C_STRANGE; return (NULL); } if ((pid = fork1()) == -1) { free(P); *perr = C_FORK; return (NULL); } if (pid == 0) { /* child process */ id_t id; extern char **environ; /* * If running setuid or setgid, reset credentials to normal. */ if ((id = getgid()) != getegid()) (void) setgid(id); if ((id = getuid()) != geteuid()) (void) setuid(id); Pcreate_callback(P); /* execute callback (see below) */ (void) pause(); /* wait for PRSABORT from parent */ /* * This is ugly. There is no execvep() function that takes a * path and an environment. We cheat here by replacing the * global 'environ' variable right before we call this. */ if (envp) environ = (char **)envp; (void) execvp(file, argv); /* execute the program */ _exit(127); } /* * Initialize the process structure. */ (void) memset(P, 0, sizeof (*P)); (void) mutex_init(&P->proc_lock, USYNC_THREAD, NULL); P->flags |= CREATED; P->state = PS_RUN; P->pid = pid; P->asfd = -1; P->ctlfd = -1; P->statfd = -1; P->agentctlfd = -1; P->agentstatfd = -1; P->ops = &P_live_ops; Pinitsym(P); /* * Open the /proc/pid files. */ (void) sprintf(procname, "/proc/%d/", (int)pid); fname = procname + strlen(procname); (void) set_minfd(); /* * Exclusive write open advises others not to interfere. * There is no reason for any of these open()s to fail. */ (void) strcpy(fname, "as"); if ((fd = open(procname, (O_RDWR|O_EXCL))) < 0 || (fd = dupfd(fd, 0)) < 0) { dprintf("Pcreate: failed to open %s: %s\n", procname, strerror(errno)); rc = C_STRANGE; goto bad; } P->asfd = fd; (void) strcpy(fname, "status"); if ((fd = open(procname, O_RDONLY)) < 0 || (fd = dupfd(fd, 0)) < 0) { dprintf("Pcreate: failed to open %s: %s\n", procname, strerror(errno)); rc = C_STRANGE; goto bad; } P->statfd = fd; (void) strcpy(fname, "ctl"); if ((fd = open(procname, O_WRONLY)) < 0 || (fd = dupfd(fd, 0)) < 0) { dprintf("Pcreate: failed to open %s: %s\n", procname, strerror(errno)); rc = C_STRANGE; goto bad; } P->ctlfd = fd; (void) Pstop(P, 0); /* stop the controlled process */ /* * Wait for process to sleep in pause(). * If the process has already called pause(), then it should be * stopped (PR_REQUESTED) while asleep in pause and we are done. * Else we set up to catch entry/exit to pause() and set the process * running again, expecting it to stop when it reaches pause(). * There is no reason for this to fail other than an interrupt. */ (void) Psysentry(P, SYS_pause, 1); (void) Psysexit(P, SYS_pause, 1); for (;;) { if (P->state == PS_STOP && P->status.pr_lwp.pr_syscall == SYS_pause && (P->status.pr_lwp.pr_why == PR_REQUESTED || P->status.pr_lwp.pr_why == PR_SYSENTRY || P->status.pr_lwp.pr_why == PR_SYSEXIT)) break; if (P->state != PS_STOP || /* interrupt or process died */ Psetrun(P, 0, 0) != 0) { /* can't restart */ if (errno == EINTR || errno == ERESTART) rc = C_INTR; else { dprintf("Pcreate: Psetrun failed: %s\n", strerror(errno)); rc = C_STRANGE; } goto bad; } (void) Pwait(P, 0); } (void) Psysentry(P, SYS_pause, 0); (void) Psysexit(P, SYS_pause, 0); /* * Kick the process off the pause() and catch * it again on entry to exec() or exit(). */ (void) Psysentry(P, SYS_exit, 1); (void) Psysentry(P, SYS_exec, 1); (void) Psysentry(P, SYS_execve, 1); if (Psetrun(P, 0, PRSABORT) == -1) { dprintf("Pcreate: Psetrun failed: %s\n", strerror(errno)); rc = C_STRANGE; goto bad; } (void) Pwait(P, 0); if (P->state != PS_STOP) { dprintf("Pcreate: Pwait failed: %s\n", strerror(errno)); rc = C_STRANGE; goto bad; } /* * Move the process through instances of failed exec()s * to reach the point of stopped on successful exec(). */ (void) Psysexit(P, SYS_exec, TRUE); (void) Psysexit(P, SYS_execve, TRUE); while (P->state == PS_STOP && P->status.pr_lwp.pr_why == PR_SYSENTRY && (P->status.pr_lwp.pr_what == SYS_execve || P->status.pr_lwp.pr_what == SYS_exec)) { /* * Fetch the exec path name now, before we complete * the exec(). We may lose the process and be unable * to get the information later. */ (void) Pread_string(P, execpath, sizeof (execpath), (off_t)P->status.pr_lwp.pr_sysarg[0]); if (path != NULL) (void) strncpy(path, execpath, len); /* * Set the process running and wait for * it to stop on exit from the exec(). */ (void) Psetrun(P, 0, 0); (void) Pwait(P, 0); if (P->state == PS_LOST && /* we lost control */ Preopen(P) != 0) { /* and we can't get it back */ rc = C_PERM; goto bad; } /* * If the exec() failed, continue the loop, expecting * there to be more attempts to exec(), based on PATH. */ if (P->state == PS_STOP && P->status.pr_lwp.pr_why == PR_SYSEXIT && (P->status.pr_lwp.pr_what == SYS_execve || P->status.pr_lwp.pr_what == SYS_exec) && (lasterrno = P->status.pr_lwp.pr_errno) != 0) { /* * The exec() failed. Set the process running and * wait for it to stop on entry to the next exec(). */ (void) Psetrun(P, 0, 0); (void) Pwait(P, 0); continue; } break; } if (P->state == PS_STOP && P->status.pr_lwp.pr_why == PR_SYSEXIT && (P->status.pr_lwp.pr_what == SYS_execve || P->status.pr_lwp.pr_what == SYS_exec) && P->status.pr_lwp.pr_errno == 0) { /* * The process is stopped on successful exec() or execve(). * Turn off all tracing flags and return success. */ restore_tracing_flags(P);#ifndef _LP64 /* We must be a 64-bit process to deal with a 64-bit process */ if (P->status.pr_dmodel == PR_MODEL_LP64) { rc = C_LP64; goto bad; }#endif /* * Set run-on-last-close so the controlled process * runs even if we die on a signal. */ (void) Psetflags(P, PR_RLC); *perr = 0; return (P); } rc = lasterrno == ENOENT ? C_NOENT : C_NOEXEC;bad: (void) kill(pid, SIGKILL); if (path != NULL && rc != C_PERM && rc != C_LP64) *path = '\0'; Pfree(P); *perr = rc; return (NULL);}struct ps_prochandle *Pcreate( const char *file, /* executable file name */ char *const *argv, /* argument vector */ int *perr, /* pointer to error return code */ char *path, /* if non-null, holds exec path name on return */ size_t len) /* size of the path buffer */{ return (Pxcreate(file, argv, NULL, perr, path, len));}/* * Return a printable string corresponding to a Pcreate() error return. */const char *Pcreate_error(int error){ const char *str; switch (error) { case C_FORK: str = "cannot fork"; break; case C_PERM: str = "file is set-id or unreadable"; break; case C_NOEXEC: str = "cannot execute file"; break; case C_INTR: str = "operation interrupted"; break; case C_LP64: str = "program is _LP64, self is not"; break; case C_STRANGE: str = "unanticipated system error"; break; case C_NOENT: str = "cannot find executable file"; break; default: str = "unknown error"; break; } return (str);}/* * Callback to execute in each child process created with Pcreate() after fork * but before it execs the new process image. By default, we do nothing, but * by calling this function we allow the client program to define its own * version of the function which will interpose on our empty default. This * may be useful for clients that need to modify signal dispositions, terminal * attributes, or process group and session properties for each new victim. *//*ARGSUSED*/voidPcreate_callback(struct ps_prochandle *P){ /* nothing to do here */}/* * Grab an existing process. * Return an opaque pointer to its process control structure. * * pid: UNIX process ID. * flags: * PGRAB_RETAIN Retain tracing flags (default clears all tracing flags). * PGRAB_FORCE Grab regardless of whether process is already traced. * PGRAB_RDONLY Open the address space file O_RDONLY instead of O_RDWR, * and do not open the process control file. * PGRAB_NOSTOP Open the process but do not force it to stop. * perr: pointer to error return code. */struct ps_prochandle *Pgrab(pid_t pid, int flags, int *perr){ struct ps_prochandle *P; int fd, omode; char procname[100]; char *fname; int rc = 0; /* * PGRAB_RDONLY means that we do not open the /proc/<pid>/control file, * and so it implies RETAIN and NOSTOP since both require control. */ if (flags & PGRAB_RDONLY) flags |= PGRAB_RETAIN | PGRAB_NOSTOP; if ((P = malloc(sizeof (struct ps_prochandle))) == NULL) { *perr = G_STRANGE; return (NULL); } P->asfd = -1; P->ctlfd = -1; P->statfd = -1;again: /* Come back here if we lose it in the Window of Vulnerability */ if (P->ctlfd >= 0) (void) close(P->ctlfd); if (P->asfd >= 0) (void) close(P->asfd); if (P->statfd >= 0) (void) close(P->statfd); (void) memset(P, 0, sizeof (*P)); (void) mutex_init(&P->proc_lock, USYNC_THREAD, NULL); P->ctlfd = -1; P->asfd = -1; P->statfd = -1; P->agentctlfd = -1; P->agentstatfd = -1; P->ops = &P_live_ops; Pinitsym(P); /* * Open the /proc/pid files */ (void) sprintf(procname, "/proc/%d/", (int)pid); fname = procname + strlen(procname); (void) set_minfd(); /* * Request exclusive open to avoid grabbing someone else's * process and to prevent others from interfering afterwards. * If this fails and the 'PGRAB_FORCE' flag is set, attempt to * open non-exclusively. */ (void) strcpy(fname, "as"); omode = (flags & PGRAB_RDONLY) ? O_RDONLY : O_RDWR; if (((fd = open(procname, omode | O_EXCL)) < 0 && (fd = ((flags & PGRAB_FORCE)? open(procname, omode) : -1)) < 0) || (fd = dupfd(fd, 0)) < 0) { switch (errno) { case ENOENT: rc = G_NOPROC; break; case EACCES: case EPERM: rc = G_PERM; break; case EBUSY: if (!(flags & PGRAB_FORCE) || geteuid() != 0) { rc = G_BUSY; break; } /* FALLTHROUGH */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -