/* * sucaps.c * * variant on sucap (from the libcap sources) which deals with linux * 2.4 kernels blatting capabilities on execve() * * Copyright (c) 2002 Tim Deegan * Based on a program by Finn Arne Gangstad * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * $Id: sucaps.c,v 1.5 2005/10/17 09:36:54 tjd21 Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef DEBUG #define TRC(_s) fprintf(stderr, __FILE__ ":%i: %s\n", __LINE__, (_s)); #else #define TRC(_s) #endif #define ROUNDUP(x,y) ((((x)+(y)-1)/(y))*(y)) int main (int argc, char **argv) { cap_t cap; cap_flag_value_t cval; uid_t uid; gid_t gid; pid_t pid; int pipefd[2], rv, status; /* Sanity check: arguments */ if (argc < 1 || argv[argc] != NULL) { fprintf(stderr, "sucaps: argc/argv are not well formed\n"); exit(1); } if (argc < 5) { fprintf(stderr, "usage: %s [args...]\n", argv[0]); exit(1); } /* Check our own capabilities */ cap = cap_get_proc(); if (!cap) { fprintf(stderr, "%s: can't read my capset: %s\n", argv[0], strerror(errno)); exit(1); } if (cap_get_flag(cap, CAP_SETPCAP, CAP_EFFECTIVE, &cval) != 0) { fprintf(stderr, "%s: cap_get_flag() failed on valid data (how odd).\n", argv[0]); exit(1); } if (cval != CAP_SET) { fprintf(stderr, "%s: you need CAP_SETPCAP to use this program.\n", argv[0]); exit(1); } cap_free(cap); /* Read requested capabilities from the command line */ cap = cap_from_text(argv[1]); if (!cap) { fprintf(stderr, "%s: Can't parse \"%s\".\n", argv[0], argv[1]); exit(1); } /* Convert username to uid */ { struct passwd *pw = getpwnam(argv[2]); if (!pw) { fprintf(stderr, "%s: No such user: %s\n", argv[0], argv[2]); exit(1); } uid = pw->pw_uid; } /* Convert groupname to gid */ { struct group *gr = getgrnam(argv[3]); if (!gr) { fprintf(stderr, "%s: No such group: %s\n", argv[0], argv[3]); exit(1); } gid = gr->gr_gid; } /* We need to synchronise parent and child */ if (pipe(pipefd) != 0) { fprintf(stderr, "%s: can't open a pipe: %s.\n", argv[0], strerror(errno)); exit (1); } /* And now, the real work begins */ if (fork() != 0) { /* Parent. This process is ptraced by its child, and execs the * victim binary. */ close(pipefd[1]); /* Get rid of any supplemental groups */ if (!getuid() && setgroups(0, NULL)) { fprintf(stderr, "%s: can't set groups: %s.\n", argv[0], strerror(errno)); exit(2); } /* Set gid and uid */ if (setregid(gid, gid) != 0 || setreuid(uid, uid) != 0) { fprintf(stderr, "%s: parent: can't set uid/gid: %s.\n", argv[0], strerror(errno)); exit(2); } /* Blocking read on the pipe for synchronisation: by the time * we return from this, the child process will have us ptrace()d */ while(read(pipefd[0], &rv, 1) == -1 && errno == EINTR); /* Fire up the victim */ execvp(argv[4], argv + 4); /* Oops: not supposed to get this far */ fprintf(stderr, "%s: parent: exec failed (%s): aborting.\n", argv[0], strerror(errno)); exit(2); } else { /* Child. This process takes control of its parent , lets it * execve() and then sets its capabilities */ close(pipefd[0]); pid = getppid(); if (ptrace(PTRACE_ATTACH, pid, NULL, NULL)) { fprintf(stderr, "%s: child: PTRACE_ATTACH failed (%s): aborting.\n", argv[0], strerror(errno)); kill(pid, SIGKILL); exit(3); } waitpid(pid, &status, 0); if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGSTOP) { fprintf(stderr, "%s: child: parent %s %i: aborting.\n", argv[0], WIFEXITED(status) ? "has exited with" : WIFSIGNALED(status) ? "was killed by signal" : "is stopped on the wrong signal:", WIFEXITED(status) ? WEXITSTATUS(status) : WIFSIGNALED(status) ? WTERMSIG(status) : WSTOPSIG(status) ); if (WIFSTOPPED(status)) kill(pid, SIGKILL); exit(3); } TRC(" child: I have control"); /* Allow the parent to unblock */ close(pipefd[1]); /* Wait until the parent process has execve()d the victim. */ if (ptrace(PTRACE_CONT, pid, NULL, NULL) != 0) { fprintf(stderr, "%s: child: PTRACE_CONT failed (%s): aborting.\n", argv[0], strerror(errno)); kill(pid, SIGKILL); exit(3); } waitpid(pid, &status, 0); if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGTRAP) { fprintf(stderr, "%s: child: parent %s %i: aborting.\n", argv[0], WIFEXITED(status) ? "has exited with" : WIFSIGNALED(status) ? "was killed by signal" : "is stopped on the wrong signal:", WIFEXITED(status) ? WEXITSTATUS(status) : WIFSIGNALED(status) ? WTERMSIG(status) : WSTOPSIG(status) ); if (WIFSTOPPED(status)) kill(pid, SIGKILL); exit(3); } TRC(" child: victim stopped after execve()"); /* Set the victim's capabilities */ if (capsetp(pid, cap) != 0) { fprintf(stderr, "%s: child: could not set caps: %s\n", argv[0], strerror(errno)); exit(3); } TRC(" child: set capabilities"); /* Release it into the wild */ if (ptrace(PTRACE_DETACH, pid, NULL, NULL) != 0) { fprintf(stderr, "%s: child: DETACH failed: how odd.\n", argv[0]); exit(3); } TRC(" child: detached from victim"); /* Success */ exit (0); } } /* * EOF */