How to avoid buffer overflow with C struct array of strings - c

I'm running into buffer overflows when reading a file in C and copying character arrays. There are three potentially offending pieces of code and I can't figure out where I'm going wrong.
The first reads a file and populates it into a hashmap:
bool load_file(const char* in_file, hmap hashtable[]) {
for(int x = 0; x < HASH_SIZE; x++) {
hashtable[x] = NULL;
}
FILE *fptr = fopen(in_file, "r");
char c[LENGTH] = "";
c[0] = '\0';
while (fgets(c, sizeof(c)-1, fptr) != NULL) {
node *n = malloc(sizeof(node));
hmap new_node = n;
new_node->next = NULL;
strncpy(new_node->content, c, LENGTH-1);
// do stuff to put it into the hashtable
}
fclose(fptr);
return true;
}
The second checks whether given content is in the hashmap:
bool check_content(const char* content, hmap hashtable[]) {
char c_content[LENGTH] = "";
strncpy(c_content, content, LENGTH-1);
// do stuff to check if it's in the hashmap
return false;
}
and the third parses a given file and checks whether its content is in the hashmap:
int check_file(FILE* fp, hmap hashtable[], char * not_found[]) {
int num_not_found = 0;
char c[1000] = "";
while (fgets(c, sizeof(c)-1, fp) != NULL) {
char * pch;
char curToken[LENGTH] = "";
pch = strtok (c," ");
strncpy(curToken, pch, LENGTH-1);
curToken[LENGTH]=0;
if(!check_content(curToken, hashtable)) {
not_found[num_not_found] = malloc(LENGTH*sizeof(not_found[num_not_found]));
strncpy(not_found[num_not_found], curToken, LENGTH-1);
num_not_found++;
}
}
fclose(fp);
return num_not_found;
}
Finally, main calls these and frees mallocs:
int main (int argc, char *argv[])
{
hmap hashtable[HASH_SIZE];
load_file(argv[2], hashtable);
FILE *fptr = fopen(argv[1], "r");
char * not_found[MAX_ENTRIES];
int num_not_found = check_file(fptr, hashtable, not_found);
for(int x=0; x<num_not_found; x++) {
free(not_found[x]);
}
for(int y=0; hashtable[y] != NULL; y++) {
free(hashtable[y]);
}
return 0;
}
My question is this: for each of the three code snippets, what have I done that causes buffer overflows? Many thanks in advance!

I finally got rid of the buffer overflow problems mostly by following David's advice in the comments, plus figuring out that I had one more malloc than I needed. The fixes were:
new_node->next needed a malloc
The malloc for new_node->next should happen only if it's actually going to be used.
not_found[num_not_found] = malloc(LENGTH*sizeof(not_found[num_not_found])); was wrong and should have been notfound[num_not_found] = malloc(sizeof(char) * (strlen(pch)+1)) (assuming pch wasn't null terminated). (Side note, for whatever reason, on my computer, malloc(sizeof(char) * strlen(pch)+1) is not the same as malloc(strlen(pch)+1))
The return of every malloc really does have to be validated.

Related

Array of structs only outputs last entry read in C

I am reading from a file and inserting the read entries into a struct as shown:
typedef struct card
{
unsigned int id;
char* name;
char* cost;
unsigned int converted_cost;
char* type;
char* text;
char* stats;
enum rarity rarity;
} card_t;
int main(int argc, char **argv) {
FILE *input_file;
input_file = fopen(argv[1], "r");
card_t **cards = NULL;
int cardsaccum = 0;
char *buf = NULL;
char *name_duplicate;
size_t bufsiz = 0;
ssize_t result = getline(&buf, &bufsiz, input_file);
while (result > 0)
{
// COPIES BUFFER TO SAVE THE MEMORY ADDRESS
char *stringp = buf;
// ALLOCATES MEMORY
cards = realloc(cards, sizeof(card_t *) * num_entries);
cards[cardsaccum] = malloc(sizeof(card_t));
name_duplicate = strsep(&stringp, "\"");;
cards[cardsaccum]->name = name_duplicate;
cardsaccum++;
num_entries++;
result = getline(&buf, &bufsiz, input_file);
}
for(int i = 0; i < cardsaccum; i++)
{
printf("%s\n",cards[i]->name);
}
// FREEING MEMORY
for(i = 0; i < cardsaccum;i++)
{
free(cards[i]);
}
free(cards);
free(buf);
fclose(input_file);
return 0;
}
The file should be reading the names of Bob, Marley, Frank. However, my output is only printing the last entry read:
Frank
Frank
Frank
Do I have a problem with my allocation of memory or is it something else? Any help is appreciated!
The function getline() calls realloc() internally. As the result, all previous pointers originating from buf like strsep(&stringp, "\"") get invalidated or overwritten.
Note that previous cards[X]->name point to some location inside previous buf.
Make a copy of a string before processing a next line:
cards[cardsaccum]->name = strdup(name_duplicate);
other solution is setting buf back to NULL just before calling getline.

C - Why does char array only return last value that's put into it?

I'm writing in C. I'm trying to read lines from a text file, parse the line, and put some info into an array of strings. When I test my code, every value in the array seems to be the last value inserted. What causes this?
int r;
char *users[51]; //given no more than 50 users
for (r = 0; r < 51; r++) {
int n = 15; //arbitrary guess at length of unknown usernames
users[r] = malloc((n + 1) * sizeof(char));
}
FILE *fp;
fp = fopen(argv[1], "r");
char *username;
int counter = 0;
char line[100];
while (fgets(line, 100, fp) != NULL) {
username = strtok(line, ":");
users[counter] = username;
printf("%s\n", username);
printf("%s\n", users[counter]);
//counter increase for later
counter += 1;
strtok is a very confusing function:
it modifies the array it receives a pointer to
it returns a pointer to an element of this array.
it keeps an internal state which makes it non-reentrant and non thread-safe.
Hence username points inside line. You store this pointer into users[counter]. At the end of the loop, all entries in users point to the same array that has been overwritten by every call to fgets().
You should duplicate the contents of the array with strdup():
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[) {
char *users[51]; //given no more than 50 users
int r;
FILE *fp;
if (argc < 2) {
fprintf(stderr, "missing filename argument\n");
return 1;
}
fp = fopen(argv[1], "r");
if (fp == NULL) {
fprintf(stderr, "cannot open file %s\n", argv[1]);
return 1;
}
char line[100];
int counter = 0;
while (counter < 50 && fgets(line, 100, fp) != NULL) {
char *username = strtok(line, ":");
if (username != NULL) {
users[counter] = strdup(username);
//counter increase for later
counter += 1;
}
}
users[counter] = NULL;
...
}
You put a sensible value into each entry in the array:
users[r] = malloc((n+1) * sizeof(char));
But then you overwrite it with a nonsensical value (a pointer into line):
users[counter] = username;
What you presumably wanted to do was copy the string pointed to by username into the space allocated at users[counter]. The strcpy function can do this.
I would just like to add to David's answer that you should also check the username string is null terminated before using strcpy(). I can't remember if strtok() null terminates but you shouldn't rely on it anyway.

Saving lines from a text file to a dynamic array of strings

This is my first year with C, so I am a bit lost.
I have the function:
void read(char** lines){
FILE *fpointer = fopen("input1.txt","r");
char *p_input = (char*) malloc(sizeof(char)*200);
int i,len;
i=0;
lines = malloc(sizeof(char*));
while( fgets(p_input,200,fpointer) ){
len = strlen(p_input);
char temp[len];
strcpy(temp,p_input);
lines[i] = temp;
i++;
}
}
and in main:
int main(){
char **lines;
read(lines);
return 0;}
And when I try printing something from the array, I face errors and the code stops, something like:
printf("%s\n",lines[0]);
Can you please tell me what is wrong.
lines = malloc(sizeof(char*));
...
lines[i] = temp;
This is wrong, you don't have enough space for an array of pointer to chars (you need to know the number of lines to reserve)
Change to something like
char **read(void) {
size_t n = file_lines;
char **lines = malloc(sizeof(char*) * n);
...
return lines;
}
int main(void) {
char **lines;
lines = read();
return 0;
}
If you dont know the number of lines before-hand you can use realloc on each iteration of the while loop.
char **read(void) {
...
char **lines = NULL;
char **tmp;
...
while (fgets(p_input,200,fpointer)) {
...
tmp = realloc(lines, sizeof(char *) * (i + 1));
if (tmp != NULL) {
lines = tmp;
} else {
return NULL;
}
lines[i] = temp;
i++;
}
return lines;
}
int main(void) {
char **lines;
lines = read();
if (lines == NULL) {
perror("read");
exit(EXIT_FAILURE);
}
return 0;
}
In the realloc() example, if you use that together with the original code:
while( fgets(p_input,200,fpointer) ){
len = strlen(p_input);
char temp[len];
...
That's a mistake because temp[len] is declared inside the loop which means it'll be destroyed upon exiting the while loop. So your entries inside your realloc() array will point to nothing.
You would want to use malloc() inside the while loop to generate a separate space for each entry rather than declare a static array like the above.
And to tidy up at the end remember to free() the space at the end before your program exits completely.

Load/fill a struct with char** array as a struct member, c

In the last two days i have asked a question to load struct, but i have a problem to access my struct out side my loop(a loop to load my struct). i have edited my question/and code this way:
myfile.txt
Biology,chemistry,maths,music
Mechanics,IT,Geology,music,Astronomy
football,vollyball,baseball
main.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define path "myfile.txt"
typedef struct student_info
{
char **cources_as_list;
} std_info;
std_info *myinfo; //a global var that will conatain student info
int line_count = 0, cource_count = 0;
char** load_file()
{
char *line = NULL;
size_t len = 0;
FILE *fp;
int indexq=0;
fp = fopen(path, "r");
if (fp == NULL)
{
perror("FILE OPEN ERROR[IN load_file]: ");
exit(1);
}
char **mydata = malloc (sizeof (char *) * 4);//aup to four elements
while (getline(&line, &len, fp) != -1)
{
strtok(line, "\n");
mydata[indexq]= strdup(line);
indexq++;
}
line_count = indexq;
return mydata;
}
char **return_cource_list(char *cources_string) {
char *token;
char **cource_list = malloc(sizeof(char *) * 10);
int index = 0;
//course_string is delimited by ",": (eg. Biology,chemistry,maths,music). parse this and add to my char ** variable.
token = strtok(cources_string, ",");
while (token != NULL)
{
cource_list[index] = strdup(token);
token = strtok(NULL, ",");
index++;
}
cource_count = index;
return cource_list;
}
int main()
{
int i, j;
char** mydata = load_file(); //returns lines as a list/char ** array from file
for (i = 0; i < line_count; i++) //line_count is the number of elements/lines in "mydata"
{
printf("line_data: %s\n",mydata[i]);//i can see all my lines!
char **std_cource_list = return_cource_list(mydata[i]);
for (j = 0; j < cource_count; j++)
{
printf("\tcourse[%d]: %s\n",j,std_cource_list[j]);//i have all my courses as a list from each line
}
//can i load my struct like this? or any option to load my struct?
myinfo[i].cources_as_list = std_cource_list;
}
// i want to see my structure elements here, (nested for loop required).
}
Am getting seg_fault error while loading my char array to my struct.
(i.e: this line: myinfo[i].cources_as_list = std_cource_list;)
You need to allocate the memory for your struct.
std_info *myinfo = malloc(sizeof(std_info));
Also don't make it global, since there is really no need for global variables in this task.
Try
std_info * myinfo = malloc(line_count * sizeof *myinfo);
This allocates memory to hold line_count objects of std_info, with myinfo pointing to the 1st.
You never allocate space for myinfo and I would suggest making it a local variable. There is almost no need for global variables except in very specific cases.
Also, you are using malloc() almost only for fixed size allocations which would be easier to manage and more efficient if you do statically in the sense that you can use arrays for that.
This might be what you're interested in
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
struct student_info
{
char **courses;
size_t size;
};
char **
load_file(const char *const path)
{
char *line;
FILE *file;
char **data;
size_t row;
size_t length;
size_t count;
file = fopen(path, "r");
if (file == NULL)
{
perror("FILE OPEN ERROR[IN load_file]: ");
return NULL; // Notify the caller that there was a problem
// but do not necessarily quit as you might
// retry with another path.
}
count = 0;
for (int chr = fgetc(file) ; chr != EOF ; chr = fgetc(file))
count += (chr == '\n') ? 1 : 0;
rewind(file);
data = malloc((count + 1) * sizeof(*data));
if (data == NULL)
{
// Perhaps notify the error
fclose(file);
return NULL;
}
data[count] = NULL; // Use as end of array delimiter
row = 0;
line = NULL;
length = 0;
while ((length = getline(&line, &length, file)) != -1)
{
// The last character is always `\n' so remove it
data[row] = malloc(length);
if (data == NULL)
{
fclose(file);
for (size_t i = row ; i >= 0 ; --i)
{
free(data[i]);
free(data);
return NULL;
}
}
data[row][length - 1] = '\0';
memcpy(data[row], line, length - 1);
++row;
}
fclose(file);
// You need to `free' this, read the documentation
free(line);
return data;
}
char **
extract_courses_as_list(const char *const input)
{
char **courses;
size_t index;
const char *tail;
const char *head;
size_t count;
head = input;
count = 0;
/* Count the number of fields to allocate memory */
while (head != NULL)
{
tail = strchr(head, ',');
if (tail != NULL)
head = tail + 1;
else
head = NULL;
count += 1;
}
index = 0;
/* Allocate memory for the list, and the sentinel */
courses = malloc((count + 1) * sizeof(*courses));
head = input;
while (head != NULL)
{
ptrdiff_t length;
/* find the next `,' in the input string */
tail = strchr(head, ',');
if (tail == NULL) /* if it's not there, it's the last one */
tail = strchr(head, '\0');
/* compute the number of characters of the field */
length = (ptrdiff_t) (tail - head);
/* allocate space to copy the string */
courses[index] = malloc(length + 1);
if (courses == NULL) /* always be safe and check */
{
for (size_t i = index ; i >= 0 ; --i)
free(courses[index]);
free(courses);
return NULL;
}
/* always remember to `null' terminate */
courses[index][length] = '\0';
/* finally, copy the string */
memcpy(courses[index], head, length);
/* check whehter it was the last field and
* update the pointer to the next one accordingly
*/
if ((tail != NULL) && (*tail != '\0'))
head = tail + 1;
else
head = NULL;
/* Don't forget the fields counter */
index++;
}
courses[count] = NULL;
return courses;
}
void
concatenate_lists(struct student_info *info, char **source)
{
char **temporary;
size_t length;
length = info->size;
for (size_t i = 0 ; source[i] != NULL ; ++i)
length++;
temporary = realloc(info->courses, length * sizeof(*temporary));
if (temporary == NULL)
return;
for (size_t i = 0 ; source[i] != NULL ; ++i)
temporary[i + info->size] = strdup(source[i]);
info->courses = temporary;
info->size = length;
}
void
free_list(char **lines)
{
if (lines == NULL)
return;
for (size_t i = 0 ; lines[i] != '\0' ; ++i)
free(lines[i]);
free(lines);
}
int
main()
{
struct student_info info;
char **lines;
lines = load_file("data.tx");
if (lines == NULL)
return -1;
info.courses = NULL;
info.size = 0;
for (size_t i = 0 ; lines[i] != NULL ; ++i)
{
char **courses;
courses = extract_courses_as_list(lines[i]);
if (courses == NULL)
continue;
concatenate_lists(&info, courses);
free_list(courses);
}
for (size_t i = 0 ; i < info.size ; ++i)
{
fprintf(stderr, "%s\n", info.courses[i]);
free(info.courses[i]);
}
free(info.courses);
free_list(lines);
return 0;
}
You will notice that I never used strdup(), the reason being that the length of the string that we want to copy is always known.

Parsing and data overwriting issues in C using custom strtok

I'm reading in a .csv file, which I then need to parse into tokens. I tried using strtok(), but that unfortunately cannot return null fields (which my data is fulll of). So I went with a home-made version of strtok that I found, strtok_single, which returns the correct values that I need.
The data is input into my array correctly; but there is something wrong because before the initilization loops finish, the data gets overwritten. I've tried print statements and analyzing the problem but I just can't figure out what's wrong. Any insight at all would be helpful.
Here is the homemade strtok function I'm using:
char* strtok_single(char* str, char const* delims) {
static char* src = NULL;
char* p, *ret = 0;
if (str != NULL)
src = str;
if (src == NULL)
return NULL;
if ((p = strpbrk(src, delims)) != NULL) {
*p = 0;
ret = src;
src = ++p;
}
return ret;
}
Here is my code:
int main() {
int numLines = 0;
int ch, i, j;
char tmp[1024];
char* field;
char line[1024];
FILE* fp = fopen("filename.csv", "r");
// count number of lines in file
while ((ch = fgetc(fp)) != EOF) {
if (ch == '\n')
numLines++;
}
fclose(fp);
// Allocate memory for each line in file
char*** activity = malloc(numLines * sizeof(char**));
for (i = 0; i < numLines; i++) {
activity[i] = malloc(42 * sizeof(char*));
for (j = 0; j < 42; j++) {
activity[i][j] = malloc(100 * sizeof(char));
}
}
// read activity file and initilize activity matrix
FILE* stream = fopen("filename.csv", "r");
i = 0;
while (fgets(line, 1024, stream)) {
j = 0;
int newlineLoc = strcspn(line, "\n");
line[newlineLoc] = ',';
strcpy(tmp, line);
field = strtok_single(tmp, ",");
while (field != NULL) {
for (j = 0; j < 42; j++) {
activity[i][j] = field;
field = strtok_single(NULL, ",");
// when I print activity[i][j] here, the values are correct
}
// when I print activity[i][j] here, the values are correct for the
// first iteration
// and then get overwritten by partial data from the next line
}
i++;
} // close while
fclose(stream);
// by the time I get to here my matrix is full of garbage
// some more code that prints the array and frees memory
} // close main
activity[i][j] = field;
When the loops finish, each activity[i][j] points to somewhere in tmp, which is overwritten in each loop. Instead, since you pre-allocate space in each activity[i][j], you should just copy the contents of the string to that:
strcpy(activity[i][j], field);
Being careful of buffer overflow (i.e. if field is more than 99 characters).
Also, the sizeof(char) is superfluous since it's always 1 by definition.
Your line "activity[i][j] = field;" is backwards - you want the pointer assigned to the malloc'd memory.

Resources