Чому це gets()
небезпечно
Перший Інтернет-черв'як ( Інтернет-черв'як Морріса ) втік близько 30 років тому (1988-11-02), і він використовував gets()
і переповнення буфера як один із його методів поширення з системи в систему. Основна проблема полягає в тому, що функція не знає, наскільки великий буфер, тому він продовжує читати, поки не знайде новий рядок або не зустріне EOF, і може переповнити межі буфера, який йому було надано.
Ви повинні забути, що коли-небудь чули, що gets()
існувало.
Стандарт C11 ISO / IEC 9899: 2011 виключений gets()
як стандартна функція, яка є «Гарною річчю ™» (вона офіційно була позначена як «застаріла» та «застаріла» в ISO / IEC 9899: 1999 / Cor.3: 2007 - Технічна корекція 3 для C99, а потім видалено в C11). На жаль, він залишатиметься в бібліотеках довгі роки (означає «десятиліття») з міркувань зворотної сумісності. Якби я залежав від мене, реалізація gets()
стала б:
char *gets(char *buffer)
{
assert(buffer != 0);
abort();
return 0;
}
Зважаючи на те, що ваш код все одно буде зламаний, рано чи пізно, краще усунути проблеми раніше, ніж пізніше. Я буду готовий додати повідомлення про помилку:
fputs("obsolete and dangerous function gets() called\n", stderr);
Сучасні версії системи компіляції Linux генерують попередження, якщо ви посилаєтесь gets()
- а також на деякі інші функції, які також мають проблеми із безпекою ( mktemp()
,…).
Альтернативи gets()
fgets ()
Як і всі інші сказали, канонічна альтернатива gets()
є fgets()
вказівка в stdin
якості файлового потоку.
char buffer[BUFSIZ];
while (fgets(buffer, sizeof(buffer), stdin) != 0)
{
...process line of data...
}
Що ще ніхто не згадував, це те, що gets()
він не включає новий рядок, але є fgets()
. Отже, можливо, вам доведеться використовувати обгортку, fgets()
яка видаляє новий рядок:
char *fgets_wrapper(char *buffer, size_t buflen, FILE *fp)
{
if (fgets(buffer, buflen, fp) != 0)
{
size_t len = strlen(buffer);
if (len > 0 && buffer[len-1] == '\n')
buffer[len-1] = '\0';
return buffer;
}
return 0;
}
Або, краще:
char *fgets_wrapper(char *buffer, size_t buflen, FILE *fp)
{
if (fgets(buffer, buflen, fp) != 0)
{
buffer[strcspn(buffer, "\n")] = '\0';
return buffer;
}
return 0;
}
Крім того, як в коментарі зазначає кафе , а у своїй відповіді показує paxdiablo , у fgets()
вас можуть залишитися дані на лінії. Мій код обгортки залишає ці дані для читання наступного разу; Ви можете легко змінити його, щоб заграти решту рядків даних, якщо Ви бажаєте:
if (len > 0 && buffer[len-1] == '\n')
buffer[len-1] = '\0';
else
{
int ch;
while ((ch = getc(fp)) != EOF && ch != '\n')
;
}
Залишилася проблема полягає в тому, як повідомити про три різних станах результату - EOF або помилку, зчитування рядків і не усікання, і часткове зчитування рядка, але дані були усічені.
Ця проблема не виникає у gets()
зв'язку з тим, що вона не знає, де закінчується ваш буфер і весело топче його кінець, приводячи хаос у вашу прекрасно доглянуту макет пам'яті, часто псуючи зворотний стек ( переповнення стека ), якщо буфер виділений на стек або топтання керуючої інформації, якщо буфер динамічно розподіляється, або копіювання даних через інші дорогоцінні глобальні (або модульні) змінні, якщо буфер розподілений статично. Жодне з них не є хорошою ідеєю - вони уособлюють словосполучення "невизначене поведінка".
Є також TR 24731-1 (Технічний звіт Комітету зі стандартів С), який надає більш безпечні альтернативи для різних функцій, включаючи gets()
:
§6.5.4.1 gets_s
Функція
Конспект
#define __STDC_WANT_LIB_EXT1__ 1
#include <stdio.h>
char *gets_s(char *s, rsize_t n);
Обмеження часу виконання
s
не має бути нульовим покажчиком. n
не має дорівнювати нулю і не перевищувати RSIZE_MAX. Символ нового рядка, кінець файлу чи помилка читання мають виникати під час читання
n-1
символів з stdin
. 25)
3 Якщо є порушення обмеження часу виконання, s[0]
встановлюється нульовий символ, і символи зчитуються та відкидаються з stdin
тих пір, поки не буде прочитаний символ нового рядка або не виникне помилка в кінці файлу чи помилка читання.
Опис
4 gets_s
Функція зчитує щонайменше на один менший, ніж кількість символів, вказана n
з потоку, на який вказує stdin
, у масив, на який вказує s
. Ніякі додаткові символи не читаються після символу нового рядка (який відкидається) або після закінчення файлу. Відхилений символ нового рядка не зараховується до кількості прочитаних символів. Нульовий символ записується відразу після того, як останній символ прочитаний в масив.
5 Якщо зустрічається кінець файлу, і в масив не читаються символи, або якщо помилка читання виникає під час операції, тоді s[0]
встановлюється нульовий символ, а інші елементи s
приймають не визначені значення.
Рекомендована практика
6 Ця fgets
функція дозволяє правильно записаним програмам безпечно обробляти вхідні рядки занадто довго, щоб зберігати їх у масиві результатів. Загалом це вимагає, щоб абоненти fgets
звертали увагу на наявність чи відсутність символу нового рядка в масиві результатів. Поміркуйте fgets
замість: (разом з будь-якою необхідною обробкою на основі символів нового рядка)
gets_s
.
25)gets_s
функція, в відміну від gets
, робить це порушення виконання-обмеження для лінії входу до переповнення буфера для його зберігання. На відміну від цього fgets
, gets_s
підтримує взаємовідносини один на один між вхідними лініями та успішними дзвінками до gets_s
. Програми, які використовують, gets
очікують таких відносин.
Компілятори Microsoft Visual Studio реалізують наближення до стандарту TR 24731-1, проте існують відмінності між підписами, реалізованими Microsoft, та тими, що знаходяться в TR.
Стандарт C11, ISO / IEC 9899-2011, включає TR24731 у Додатку K як необов'язковий компонент бібліотеки. На жаль, вона рідко реалізована на системах, схожих на Unix.
getline()
- POSIX
POSIX 2008 також забезпечує безпечну альтернативу gets()
дзвінкам getline()
. Він розподіляє простір для рядка динамічно, тож вам потрібно звільнити її. Отже, це знімає обмеження на довжину лінії. Він також повертає довжину даних, які були прочитані, або -1
(а не EOF
!), А це означає, що нульові байти на вході можна обробляти надійно. Існує також варіант "вибрати свій власний однозначний роздільник" getdelim()
; це може бути корисно, якщо ви маєте справу з результатом, find -print0
звідки кінці назв файлів позначені '\0'
, наприклад, символом NUL ASCII .
gets()
Buffer_overflow_attack