I am trying to write a little program, which is resistant against buffer overflow and similar vulnerabilities.
Since we cannot trust the user input, I thought it would be a good idea to concatenate all the strings that are inputted into one string and then pass it to a static path, which is a bash script in the same folder and then add all the parameters/flags/arguments (e.g. ./script.sh test1 test2 test3 test4).
My logic on paper is the following:
- Check whether the number of argc is 5 exactly (so program name + the 4 arguments), if not - exit immediately
- The specific char array for 5 strings (so array elements) with a max length of 4096 bytes length is initialized.
- Since
argv[0]is equal to the program name, we need to skip it. Hence the loop starts at 1 (first argument) and ends at argc-1. So good so far - We append the argv[1] string the ending
.keyin-memory. - We
memcpyall the strings to make it safe to use - For each iteration we don't forget to add the null terminator at the end of the string.
- After we have all the arguments safe to use we concat it in the function parse_output and call the bash script called script.sh and add all the required arguments
- We return the output from the
script.sh 4argshereto the user
I tried freeing the memory like in the comments but it seems to not work or I have errors somewhere else.
My segfaulting Proof of Concept:
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#define BUFSIZE 1000
char *concatenate(size_t size, char *array[size], const char *joint);
char *concatenate(size_t size, char *array[size], const char *joint){
size_t jlen, lens[size];
size_t i, total_size = (size-1) * (jlen=strlen(joint)) + 1;
char *result, *p;
for(i=0;i<size;++i){
total_size += (lens[i]=strlen(array[i]));
}
p = result = malloc(total_size);
for(i=0;i<size;++i){
memcpy(p, array[i], lens[i]);
p += lens[i];
if(i<size-1){
memcpy(p, joint, jlen);
p += jlen;
}
}
*p = '\0';
return result;
}
int parse_output(char *safeargv[]) {
char safeargs = *concatenate(5, safeargv, " ");
char cmd[BUFSIZE];
snprintf(cmd, BUFSIZE, "./script.sh %s", safeargs);
char buf[BUFSIZE] = {0};
FILE *fp;
if ((fp = popen(cmd, "r")) == NULL) {
printf("Error opening pipe!\n");
//free(safeargs);
return -1;
}
while (fgets(buf, BUFSIZE, fp) != NULL) {
printf("OUTPUT: %s", buf);
}
if (pclose(fp)) {
printf("Command not found or exited with error status\n");
//free(safeargs);
return -1;
}
//free(safeargs);
return 0;
}
int main(int argc, char *argv[]) {
if(argc != 5) {
exit(1);
}
char *safeargv[5][4096];
for (int i = 1; i < argc - 1; i++) {
if (i == 1)
strcat(argv[1], ".key");
for (int x = 0; x < strlen(argv[i]); x++) {
char *unsafe_string = argv[i];
size_t max_len = 4096;
size_t len = strnlen(unsafe_string, max_len) + 1;
char *x = malloc(len * sizeof(char));
if (x != NULL) {
strncpy(x, unsafe_string, len);
x[len-1] = '\0'; // Ensure null-termination
strcpy(safeargv[i], x);
free(x);
}
}
}
parse_output(safeargv);
return 0;
}
The warnings I get while compiling:
chal.c: In function 'main':
chal.c:78:32: warning: passing argument 1 of 'strcpy' from incompatible pointer type [-Wincompatible-pointer-types]
78 | strcpy(safeargv[i], x);
| ~~~~~~~~^~~
| |
| char **
In file included from chal.c:2:
/usr/include/string.h:141:39: note: expected 'char * restrict' but argument is of type 'char **'
141 | extern char *strcpy (char *__restrict __dest, const char *__restrict __src)
| ~~~~~~~~~~~~~~~~~^~~~~~
chal.c:83:18: warning: passing argument 1 of 'parse_output' from incompatible pointer type [-Wincompatible-pointer-types]
83 | parse_output(safeargv);
| ^~~~~~~~
| |
| char * (*)[4096]
chal.c:32:24: note: expected 'char **' but argument is of type 'char * (*)[4096]'
32 | int parse_output(char *safeargv[]) {
| ~~~~~~^~~~~~~~~~
It seems only my argc check works, because if I call ./programname abc abc abc abc it segfaults. Also what's the proper way to detect an error, if for case there's a typo in the argument for the script?
What mistake(s) did I make?
My last ltrace lines:
strlen(" ") = 1
strlen(nil <no return ...>
It looks as if it would try to detect the length of a null character or similar.