I'm attempting to create a replacement shell for /bin/bash, that only permits commands defined by a read only config file. These commands will be run by absolute paths, and take no arguments. I'm not an experienced C programmer, so I kindly ask for your comments and feedback. I'm especially concerned with buffer overflows etc. Please note that the "re read" config file is by design, if the rules change I would like the sessions already running to adhere to these changes.
#include <stdio.h> /* fopen et al */
#include <string.h> /* strcmp, strlen */
#include <unistd.h> /* access */
#include <stdlib.h> /* system */
#define RULEFILE "/home/ole/wannabeshell/rules.conf"
#define PROMPT "$ "
#define BUFFER 512
// not really a shell - just executes commands
// thats given in the config file by absolute
// paths..
int main (int argc, char **argv) {
// buffer size + 1 for \0 ending
char sbuffer[BUFFER + 1], fbuffer[BUFFER + 1];
// open the rulefile
FILE *fh = fopen(RULEFILE, "r");
// give up if unable to open it
if (fh == NULL) {
perror("unable to open rulefile");
return(EXIT_FAILURE);
}
while (1) {
// print prompt character
printf(PROMPT);
// force flush
fflush(NULL);
// make sure buffers does not contain data
// from previous runs of the loop
memset(sbuffer, 0, sizeof(sbuffer));
memset(fbuffer, 0, sizeof(fbuffer));
// read up to sizeof buffer, or \n
if (fgets(sbuffer, sizeof(sbuffer), stdin) == NULL) {
break;
}
// check for data up to NULL, must be atleast one !NULL
// char for this test to be successful
if (strlen(sbuffer) < 1) {
break;
}
// find and replace \r and \n if exists
sbuffer[strcspn(sbuffer, "\r\n")] = 0;
// time to exit?
if (strcmp(sbuffer, "exit") == 0 || strcmp(sbuffer, "quit") == 0) {
break;
}
// seek to start of rulefile
fseek(fh, 0, SEEK_SET);
// read one line from rule file
while (fgets(fbuffer, sizeof(fbuffer), fh) != NULL) {
// strip \n and \r from line
fbuffer[strcspn(fbuffer, "\r\n")] = 0;
// if requested command equals rulefile line
if (strlen(fbuffer) == strlen(sbuffer) && strcmp(fbuffer, sbuffer) == 0) {
// make sure the command e.g. file exists
// and is actually executable
if (access(fbuffer, X_OK) == 0) {
// fork and execute fbuffer with /bin/sh -c
// system blocks till finished
system(fbuffer);
break;
} else {
// not executable or does not exist
break;
}
}
}
}
// close the rulefile
fclose(fh);
// indicate success
return(0);
}
An initial attempt to overflow by perl -e 'print "\0"x50000;' did not reveal anything apparent. I've also tested that it runs as it should.
Example run
[ole@playground ~]$ ./strictshell
$ foo
$ \0
$ bar
$ /usr/bin/uptime
15:14:44 up 157 days, 1:04, 23 users, load average: 0.13, 0.15, 0.10
$ exit