Skip to content

Instantly share code, notes, and snippets.

@RogerGee
Last active March 10, 2021 22:25
Show Gist options
  • Save RogerGee/2eb78e1139aba58c7cbf to your computer and use it in GitHub Desktop.
Save RogerGee/2eb78e1139aba58c7cbf to your computer and use it in GitHub Desktop.
Runs a command in a restricted context (e.g. no network access, limited memory, chroot)
/* restrict-command.c - must run as setuid program */
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <error.h>
#include <getopt.h>
#include <sys/wait.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sched.h>
#include <unistd.h>
#include <fcntl.h>
/* options */
static struct restrict_options
{
bool denyNetwork;
struct rlimit limitMemory;
struct rlimit limitCPU;
struct rlimit limitFileCreate;
struct rlimit limitProcesses;
char chroot[4096];
} globalOptions;
static const char* OPTSTRING = ":hnm:c:f:p:r:";
static struct option LONG_OPTIONS[] = {
{ "help", no_argument, NULL, 0 },
{ "network", no_argument, NULL, 1 },
{ "memory", required_argument, NULL, 2 },
{ "cpu", required_argument, NULL, 3 },
{ "filesize", required_argument, NULL, 4 },
{ "processes", required_argument, NULL, 5 },
{ "chroot", required_argument, NULL, 6 },
{ 0, 0, NULL, 0 }
};
static void restrict_options_init(struct restrict_options* opts);
static void usage(const char* self);
static int exec_command(const char* program,char** argv);
static void apply_restrictions();
int main(int argc,char* argv[])
{
int o;
int ret;
const char* progName;
struct stat st;
progName = argv[0];
if (argc <= 1) {
usage(progName);
exit(EXIT_SUCCESS);
}
restrict_options_init(&globalOptions);
while (true) {
ret = getopt_long(argc,argv,OPTSTRING,LONG_OPTIONS,NULL);
if (ret == -1)
break;
o = optarg ? atoi(optarg) : -1;
if (optarg && (o <= 0 && ret != 6 && ret != 'r')) {
fprintf(stderr,"%s: invalid argument '%s': argument is out of range\n",progName,optarg);
exit(EXIT_FAILURE);
}
switch (ret) {
case 0:
case 'h':
usage(progName);
exit(EXIT_SUCCESS);
case 1:
case 'n':
globalOptions.denyNetwork = true;
break;
case 2:
case 'm':
globalOptions.limitMemory.rlim_cur = o;
globalOptions.limitMemory.rlim_max = o;
break;
case 3:
case 'c':
globalOptions.limitCPU.rlim_cur = o;
globalOptions.limitCPU.rlim_max = o;
break;
case 4:
case 'f':
globalOptions.limitFileCreate.rlim_cur = o;
globalOptions.limitFileCreate.rlim_max = o;
break;
case 5:
case 'p':
globalOptions.limitProcesses.rlim_cur = o;
globalOptions.limitProcesses.rlim_max = o;
break;
case 6:
case 'r':
/* copy file path to buffer; verify that the directory
exists and can be accessed (searched) by others (at
least) */
strcpy(globalOptions.chroot,optarg);
/* stat file: if fails then probably doesn't exist */
if (stat(globalOptions.chroot,&st) == -1) {
fprintf(stderr,"%s: error: specified chroot is invalid: %s\n",progName,strerror(errno));
exit(EXIT_FAILURE);
}
/* verify is directory */
if (!S_ISDIR(st.st_mode)) {
fprintf(stderr,"%s: error: specified chroot is not a directory\n",progName);
exit(EXIT_FAILURE);
}
/* make sure search access is granted to others */
if ((st.st_mode & S_IXOTH) == 0) {
fprintf(stderr,"%s: error: specified chroot is not searchable by others\n",progName);
exit(EXIT_FAILURE);
}
break;
case ':':
fprintf(stderr,"%s: error: missing option argument\n",progName);
exit(EXIT_FAILURE);
default:
fprintf(stderr,"%s: error: unrecognized command-line option\n",progName);
exit(EXIT_FAILURE);
}
}
if (optind >= argc) {
fprintf(stderr,"%s: error: expected command argument\n",progName);
exit(EXIT_FAILURE);
}
return exec_command(argv[optind],argv+optind);
}
void restrict_options_init(struct restrict_options* opts)
{
opts->denyNetwork = false;
memset(&opts->limitMemory,0,sizeof(struct rlimit));
memset(&opts->limitCPU,0,sizeof(struct rlimit));
memset(&opts->limitFileCreate,0,sizeof(struct rlimit));
memset(&opts->limitProcesses,0,sizeof(struct rlimit));
memset(&opts->chroot,0,sizeof(opts->chroot));
}
void usage(const char* self)
{
printf("usage: %s [options] command [arguments]\n",self);
printf(
"\nOptions\n"
"%-18s%40s\n"
"%-18s%40s\n"
"%-18s%40s\n"
"%-18s%40s\n"
"%-18s%40s\n"
"%-18s%40s\n",
" -n, --network",
"deny network access",
" -m, --memory N",
"limit memory usage to N bytes",
" -c, --cpu N",
"limit CPU time to N seconds",
" -f, --filesize N",
"limit file create size to N bytes",
" -p, --processes N",
"limit number of child processes to N",
" -r, --chroot PATH",
"chroot to PATH" );
}
int exec_command(const char* program,char** argv)
{
/* apply restrictions in the calling process */
apply_restrictions();
/* execute program to replace this process */
execvp(program,argv);
/* control is here if an error occurred */
error(0,0,"error executing '%s': %s",program,strerror(errno));
return EXIT_FAILURE;
}
void apply_restrictions()
{
int ret = -1;
uid_t uid;
if (globalOptions.denyNetwork) {
/* create a new network namespace to disallow network
connections */
ret = unshare(CLONE_NEWNET);
if (ret == -1)
error(EXIT_FAILURE,errno,"fail unshare()");
}
if (globalOptions.limitMemory.rlim_max) {
/* limit the process's virtual address space; this causes
memory allocations to fail for a variety of different
mechanisms */
ret = setrlimit(RLIMIT_AS,&globalOptions.limitMemory);
if (ret == -1)
error(EXIT_FAILURE,errno,"fail setrlimit()");
}
if (globalOptions.limitCPU.rlim_max) {
/* limit the process's total CPU time */
ret = setrlimit(RLIMIT_CPU,&globalOptions.limitCPU);
if (ret == -1)
error(EXIT_FAILURE,errno,"fail setrlimit()");
}
if (globalOptions.limitFileCreate.rlim_max) {
/* limit the maximum file size */
ret = setrlimit(RLIMIT_FSIZE,&globalOptions.limitFileCreate);
if (ret == -1)
error(EXIT_FAILURE,errno,"fail setrlimit()");
}
if (globalOptions.limitProcesses.rlim_max) {
/* limit the maximum file size */
ret = setrlimit(RLIMIT_NPROC,&globalOptions.limitProcesses);
if (ret == -1)
error(EXIT_FAILURE,errno,"fail setrlimit()");
}
if (globalOptions.chroot[0] != 0) {
ret = chroot(globalOptions.chroot);
if (ret == -1)
error(EXIT_FAILURE,errno,"fail chroot()");
}
if (ret == -1)
error(EXIT_FAILURE,0,"error: no restrictions were specified");
/* lower our effective uid back to the real uid of this process;
this causes us to lose our privileged capabilities */
uid = getuid();
seteuid(uid);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment