C ловит переполнение буфера strcat

Эта подпрограмма принимает три пользовательских ввода: текстовую строку, путь к файлу и флаг из 1 цифры. Он загружает файл в буфер, а затем добавляет и флаг, и файловый буфер в указанном порядке к массиву символов, который служит полезной нагрузкой. Он возвращает полезную нагрузку и исходную строку пользователя.

Я получил ошибку, из-за которой некоторые из моих строковых операций с файловым буфером, флагом и полезной нагрузкой, по-видимому, повреждали память, в которой находилась user_string. Я исправил ошибку, заменив strcat(flag, buffer) на strcpy(payload, flag) (это то, что я изначально намеревался написать ), но я все еще недоумеваю, что вызвало эту ошибку.

Мое предположение, прочитанное в документации (https://www.gnu.org/software/libc/manual/html_node/Concatenating-Strings.html , https://www.gnu.org/software/libc/manual/html_node/Concatenating-Strings.html) состоит в том, что strcat расширяет to строку strlen(to) байт в незащищенную память, содержимое которой загружается в буфер, копируется при переполнении буфера.

Мои вопросы:

  1. Верна ли моя догадка?

  2. Есть ли способ надежно предотвратить это? Отлов такого рода вещей с помощью проверки if(){} ненадежен, поскольку он не всегда возвращает что-то явно неправильное; вы ожидаете строку длиной filelength+1 и получаете строку filelength+1.

  3. бонус/не связанный: есть ли какие-либо вычислительные затраты/недостатки/эффекты при вызове переменной без работы с ней?

/*
user inputs:
argv[0] = tendigitaa/four
argv[1] = ~/Desktop/helloworld.txt
argv[2] = 1

helloworld.txt is a text file containing (no quotes) : "Hello World"
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <string.h>

int main (int argc, char **argv) {

    char user_string[100] = "0";
    char file_path[100] = "0";
    char flag[1] = "0";

    strcpy(user_string, argv[1]);
    strcpy(file_path, argv[2]);
    strcpy(flag, argv[3]);

    /*
    at this point printfs of the three declared variables return the same as the user inputs.

    ======
    ======
    a bunch of other stuff happens...
    ======
    ======
    and then this point printfs of the three declared variables return the same as the user inputs.
    */

    FILE *file;
    char * buffer = 0;
    long filelength;

    file = fopen(file_path, "r");

    if (file) {
        fseek(file, 0, SEEK_END);
        filelength = ftell(file);
        fseek(file, 0, SEEK_SET);
        buffer = malloc(filelength);
        printf("stringcheck1: %s \n", user_string);
        if (buffer) {
            fread(buffer, 1, filelength, file);
        }
    }

    long payloadlen = filelength + 1;
    char payload[payloadlen];
    printf("stringcheck2: %s \n", user_string);
    strcpy(payload, flag);
    printf("stringcheck3: %s \n", user_string);
    strcat(flag, buffer);
    printf("stringcheck4: %s \n", user_string); //bug here
    free(buffer);
    printf("stringcheck5: %s \n", user_string);

    payload; user_string; //bonus question: does this line have any effect on the program or computational cost?

    return 0;
}

/*
printf output:

stringcheck1: tendigitaa/four
stringcheck2: tendigitaa/four
stringcheck3: tendigitaa/four
stringcheck4: lo World
stringcheck5: lo World
*/

примечание: удаление этого раздела из основной программы привело к segfault stringcheck 4 вместо возврата "lo World". В остальном поведение было эквивалентным.


person garter_snake    schedule 12.06.2020    source источник
comment
А что ваш компилятор говорит о бонусном вопросе? Ответ: предупреждение: заявление без эффекта.   -  person 0___________    schedule 12.06.2020
comment
Что такое незащищенная память и как она связана с вопросом strcat?   -  person 0___________    schedule 12.06.2020


Ответы (3)


strcat делает именно то, что написано в документации:

char *strcat(char *restrict s1, const char *restrict s2); Функция strcat() должна добавить копию строки, на которую указывает s2 (включая завершающий нулевой байт), в конец строки, на которую указывает s1. Начальный байт s2 перезаписывает нулевой байт в конце s1. Если копирование происходит между перекрывающимися объектами, поведение не определено.

s1 должно быть выделено достаточно памяти для размещения обеих строк плюс закрывающий nul

Связанная статья посвящена программированию собственных функций конкатенации строк. Как написать такую ​​функцию, зависит от приложения, которое там указано. Есть много способов.

В вашей программе целевой массив символов недостаточно велик, и результатом является неопределенное поведение, и он даже недостаточно велик для размещения одного строка символов.

Я настоятельно рекомендую изучить некоторые строки C.

Если вам нужна более безопасная strcat, вы можете написать свою собственную, например:

char *mystrcat(const char *str1, const char *str2)
{
    char *dest = NULL;
    size_t str1_length, str2_length;

    if(str1 && str2)
    {
        dest = malloc((str1_length = strlen(str1)) + (str2_length = strlen(str2)) + 1);
        if(dest)
        {
            memcpy(dest, str1, str1_length);
            memcpy(dest + str1_length, str2, str2_length);
        }
    }
    return dest;
}

Но за безопасность мы всегда расплачиваемся — код длиннее и менее эффективен. Язык C был разработан, чтобы быть максимально эффективным, жертвуя безопасностью и вводя идею неопределенного поведения.

person 0___________    schedule 12.06.2020

Вы не можете хранить непустую строку в односимвольном массиве. Строка требует места для содержимого строки и нулевого терминатора.

Итак, когда вы объявляете

char flag[1] = "1";

вы выделили только один байт, который содержит символ 1. Нет нулевого терминатора.

Использование этого с любыми строковыми функциями приведет к неопределенному поведению, потому что они ищут нулевой терминатор, чтобы найти конец строки.

strcat(flag, buffer) будет искать нулевой терминатор, который будет вне массива, а затем добавит buffer после этого. Таким образом, это явно вызывает переполнение буфера при записи.

strcpy(payload, flag) тоже неправильно. Он будет искать нулевой терминатор после flag байтов, чтобы знать, когда прекратить копирование в payload, поэтому он будет копировать больше, чем просто flag (если только после него не будет нулевого байта).

Вы можете решить проблему strcpy(), увеличив размер:

char flag[2] = "1";

Вы также можете оставить размер пустым, компилятор сделает его достаточно большим, чтобы вместить строку, которая его инициализирует, включая нулевой байт:

char flag[] = "1";
person Barmar    schedule 12.06.2020

Строка, которая вызывает проблему, заключается в том, что strcat() пытается втиснуть буфер в флаг, длина которого составляет всего один символ, и вы не выделили больше места для размещения буфера.

Если вы хотите поместить буфер в флаг, я рекомендую использовать realloc(), чтобы увеличить длину флага, чтобы включить длину буфера.

Также единственное, что вы когда-либо выводили, это user_string. Я не уверен, что вы пытаетесь напечатать другую строку, с которой работаете.

person Daniel Smith    schedule 12.06.2020
comment
Вы не можете использовать realloc() в массиве, это должен быть указатель. - person Barmar; 12.06.2020
comment
Как указано в ответе Бармара, буфер flag недостаточно велик даже для хранения строки с одним символом. Из-за этого содержимое flag не завершается нулем. Таким образом, проблема не столько в том, что flag нужно больше места для размещения buffer, а скорее в том, что это не строка в первую очередь (поскольку строки должны заканчиваться нулем). По этой причине, даже если бы buffer была пустой строкой, операция strcat вызвала бы неопределенное поведение. - person Andreas Wenzel; 12.06.2020