This software aims to be a UNIX tool for generic secure usage when in need of privilege escalation. It is designed to run SUID, with "super-user powers" to execute things as root on the system it is installed. As such, it is designed for security, leveraging all possible measures to avoid vulnerabilities, including the reduction of complexity in its own source-code.
The purpose of sud
is to execute commands as root (super-user) or as
other users that are configured on the system. For the configuration
of authorized users it relies on their belonging to groups wheel
,
sud
or sudo
, for info see vigr(8)
or setuid(2)
.
For more general instructions, start from the homepage of SUD.
The only source file is sud.c
.
The overall structure of sud.c
is simple:
We want to have as less requirements as possible, so this list should
be kept short and eventually include #ifdef
directives for specific
platform targets.
#define _DEFAULT_SOURCE 1 #include <stdio.h> #include <stddef.h> #include <stdlib.h> #include <errno.h> #include <string.h> // lstat #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <fcntl.h> // getpwnam #include <pwd.h> // getgrouplist #include <grp.h> // local parg module #include "src/parg.h" // release stamp #ifdef RELEASE #include "stamp.h" #endif
Used in section 2
#define VERSION "1.0.0" #define OK 0 // status code for successful run #define usage_error 1 // status code for command not found #define ERR(fstr,...) { fprintf(stderr,fstr, ##__VA_ARGS__); fputc('\n',stderr); } #define XXX(fstr,...) { fprintf(stderr,fstr, ##__VA_ARGS__); fputc('\n',stderr); } #define ACT(fstr,...) { fprintf(stdout,fstr, ##__VA_ARGS__); fputc('\n',stdout); } // maximum length of a command string #define MAXCMD 512 #define PATH_MAX 1024
Used in section 2
Now we come to the general layout of the main()
function.
int main(int argc, char **argv) { {Declare variables, 5} {Parse command-line options, 5} {Verify privileged access, 5} {Execute the command, 5} {Print any errors, 5} }
Used in section 2
// full path to command char fullcmd[PATH_MAX] = {0x0}; // verify target command executable struct stat st; // recognize current user int uid; struct passwd *pw=0x0; // target privilege int target_uid=0; // cycling through groups to verify authorization int ngroups = 0; struct group* gr; // authorization flag register short authorized = 0;
Commandline option parsing is made using the excellent parg library by Jørgen Ibsen also released in public domain
int c, optind; struct parg_state ps; parg_init(&ps); while ((c = parg_getopt(&ps, argc, argv, "hvu:")) != -1) { if(fullcmd[0]) break; switch (c) { case 1: // stop to parse options, save the command and parse its arguments if(!fullcmd[0]) { struct stat tst; char file[PATH_MAX]; char *p, *path = getenv ("PATH"); if (path) // Check if command is found in $PATH for (p = path; *p; p++) { if (*p==':' && (p>path&&*(p-1)!='\\')) { *p = 0; snprintf (file, sizeof (file)-1, "%s/%s", path, ps.optarg); if (!lstat (file, &tst)) { // command found snprintf(fullcmd,PATH_MAX,"%s",file); optind = ps.optind-1; break; } *p = ':'; path = p+1; } } } break; case 'h': help: ACT("Usage: %s [-h] [-v] [-u USER] COMMAND",argv[0]); return OK; break; case 'v': ACT("Sud version %s", VERSION); #ifdef RELEASE ACT("%s (sha512)",SHA512_SUD_C); ACT("built on %s",BUILD_TIME); #endif return OK; break; case '?': ERR("unknown option -%c", ps.optopt); return usage_error; break; case 'u': { struct passwd *puid; errno = 0; puid = getpwnam(ps.optarg); if(!puid && errno) ERR("error in %s: getpwnam",__func__); if(puid) target_uid = puid->pw_uid; } break; default: ERR("error: unhandled option -%c", c); return usage_error; break; } } // for (c = ps.optind; c < argc; ++c) { // ERR("leftover '%s'", argv[c]); // } if(argc==1) goto help; // XXX("optind: %i",ps.optind);
// get the called UID and GID uid = getuid (); // get the username string from /etc/passwd pw = getpwuid( uid ); if(!pw) { ERR("uid not found in passwd: %s",strerror(errno)); return usage_error; } // one could maintain a log of calls here // XXX("sud %s called by %s(%d) gid(%d)\n", // fullcmd, pw?pw->pw_name:"", uid, gid); // command does not exist as binary on the filesystem if (lstat (fullcmd, &st) == -1) { ERR("cannot stat command: %s", fullcmd); return usage_error; } if (st.st_mode & 0022) { // command has wrong permissions (writable to others) ERR("cannot run a binary others can write: %s", fullcmd); return usage_error; } // get number of groups first getgrouplist(pw->pw_name, pw->pw_gid, NULL, &ngroups); gid_t groups[ngroups]; // get the list of groups getgrouplist(pw->pw_name, pw->pw_gid, groups, &ngroups); //example to print the groups name for (int i = 0; i < ngroups; i++){ gr = getgrgid(groups[i]); if(!gr) { ERR("getgrgid error: %s",strerror(errno)); return usage_error; } if(strncmp(gr->gr_name,"wheel",5)==0) authorized = 1; if(strncmp(gr->gr_name,"sudo",4)==0) authorized = 1; if(strncmp(gr->gr_name,"sud",3)==0) authorized = 1; } if(!authorized) { ERR("user not authorized: %s",pw->pw_name); return usage_error; }
// privilege escalation if (setuid (target_uid) <0) { ERR("setuid: %s",strerror(errno)); return usage_error; } if (seteuid (target_uid) <0) { ERR("seteuid: %s",strerror(errno)); return usage_error; } // turn current process into the execution of command // XXX("execve: %s",fullcmd); execve(fullcmd, &argv[optind], NULL); //&argv[ps.optind], NULL);
// execv returns only on errors ERR("execv: %s", strerror(errno));