Потік переливів залишаються одним з найбільш стійких і небезпечних вразливостей безпеки в C програмування. Незважаючи на те, що добре здогадані протягом десятиліть, вони продовжують викликати серйозні проблеми, такі як корупція даних, системні аварійні ситуації і віддалене виконання коду. Написання захищеного C коду вимагає глибокого розуміння того, як відбувається перелив буфера і дисциплінований підхід до запобігання їх. Ця стаття забезпечує всебічний посібник для написання надійних, переповнених C коду, покриття фундаментальних концепцій, безпечних функцій, методики валідації, компілярні захисти, і сучасні оборонні заходи.

Розуміння буферних переливів

Після того, як буфер переповнений, коли програма записує більше даних до контигузованого блоку пам'яті (буфер), ніж буфер був виділений для утримання. Оскільки буфери, що знаходяться в стеці або запашної пам'яті, перевищивши їх межі перезаписів сусідніх пам'яток. Ця корупція може змінити стан програми, ввести непередбачувана поведінка, або бути експлуатований атакатором, щоб ін'єкцій і виконати довільний код.

Наслідки залежать від того, що отримує перезапис. Поверніть адресу повернення на стеку, може перенаправити виконання на керований код атаки. Овердравлічні точни можуть призвести до до довільної пам'яті. Навіть прості аварійні випадки можуть бути важелі для атаки відмовостійкої служби. Розуміння механіки є першим кроком для запобігання.

Стейк-Базовані переливи

Місцеві змінні, включаючи буфери, заявлені всередині функції, зберігаються на стек. Стк також має адресу повернення, збережені пункти кадрів та інші дані керування. Коли лінійний буфер, як є перебігом, дані прокладає в адресу повернення та за її межами. Класичні експлуатаційні функції, такі як Morris черв'яч (1988), що використовується у вигляді переливів для отримання несанкціонованого доступу.

Капітальні переливи

Динамічно виділені буфери (через , ], і т.д.) на шпигу. Надлишок тут може бути пошкоджені метадані, які використовуються алокатором, що призводить до аварійного або експлуатації через розпилення або використання-після без атак. Кап надбавка важче використовувати, але небезпечна.

Загальні функції та їх безпечні альтернативи

Бібліотека C надає ряд функцій, які не виконують перевірки меж. Використовуючи їх найбільш поширеною причиною переливів буферів. Заміна їх з аналогами безпечніше є фундаментальною кращою практикою.

Статус на сервери

  • — Небезпечний: копії до моменту завершення нулі, не обмеження довжини.
    Альтернатива Сахєф:]
    — копії більшості n символів; зауважити, що це не null-termin, якщо джерело більше n, тому завжди вручну null-termin.
  • Ще краще: — доступний на BSD і багато Linux-систем; завжди null-termins і повертає довжину початкового рядка для виявлення викривлення.
  • — Небезпечний: concatenates без меж.
    Альтернатива Сайф: — Додатки більшості n символів і завжди null-termins.

Форматований вихід і вхід

  • — Небезпечний: пише форматований вихід на буфер з невірним контрольом.
    Альтернатива Сахєф: — вихід до значень-1 символів плюс null термінінатор.
  • — Схожі ризики; використання .
  • — Надзвичайно небезпечні; вилучені з C11 стандарту. Використовуйте замість.
  • — Немає меж перевіря. Використовуйте або з ширині поля вивірка.

Копіювати пам'ять і перемістити

  • — Сейф тільки якщо n перевірено не перевищення габаритів буфера.
    Альтернатива Сфера: ] (ручки перекриття) і завжди забезпечують n ≤ дест розмір.
  • Деякі платформи надають від Annex K (опція в C11), але прийняття обмежене.

Управління термінами та розмірами

Навіть з безпечними функціями, необхідно вводити довжини введення, забезпечити належні розміри буфера, і обробляти потенціал викривлення витончено.

Перевірити вхідні довжини

Перед копіюванням або обробкою зовнішнього входу (введення користувачів, мережеві дані, вміст файлів), визначення максимальної допустимої довжини та відхилення або припинення даних, що перевищує його. Наприклад:

#define MAX_INPUT 255
char buffer[MAX_INPUT + 1]; // +1 for null
if (strlen(user_input) > MAX_INPUT) {
 // Handle error: reject or truncate
 fputs("Input too long", stderr);
 return -1;
}
strncpy(buffer, user_input, sizeof(buffer) - 1);
buffer[sizeof(buffer) - 1] = '\0';

Використовуйте Фіксовані кріпильні манжети з відомими лімітами

Якщо це можливо, визначаються манжети з постійним розміром і їх виконання по всій коді. Уникайте змінних-довжинних масивів (VLAs), які можуть викликати переливи стека, якщо поставляються великі розміри. Замість цього виділяють динамічно з явними розмірами.

Ручка Трункція Експлуатована

Функції, такі як і ] можуть truncate дані. Усвідомляйте значення повернення для виявлення викривлення та вирішіть, чи прийнятні дані, або якщо необхідно піднятися помилка. Ігнорування траншування може залишити буфери в несподіваному стані.

Прапори з безпеки та захист від часу

Сучасні компілятори пропонують прапори, які додають відтікання буферів і пом'якшення без змін коду. Увімкніть їх у вашій системі побудови.

  • / — Вставки стекових канар (позначеннях) перед поверненням адрес. Якщо буфер переливає канар перед зміною адреси повернення, програма абортів перед завершенням експлуатації.
  • — Замінює виклики на небезпечні функції, такі як і з перевіреними версіями, які призводять до того, чи є пункт призначення буфера занадто невелика. Вимагає або більш високу оптимізацію.
  • — Уорнс про форматні рядки, які можуть призвести до переливів буферів або витоків інформації.
  • — Код інструментів для виявлення переливів буфера, без використання та інших помилок пам'яті в режимі runtime. Витрата потоків, але нездійснена для тестування.
  • — Уникає оптимізації перевірок переливу (з обережністю з обережністю).

Захисти операційної системи

Стекові каністри – це лише один шар. Технології для пом’якшення витрат у сучасних ОС включають:

  • Data Execution (DEP) / NX bit — Марки стека і шепа як нездійснюваний, запобігаючи виконання оболонок.
  • Додаток Space Layout Випадковування (ASLR) — Випадкові адреси пам'яті (stack, heap, загальні бібліотеки) для того, щоб зробити його більш важким для прогнозування цілей.
  • Relocation Read-Only (RELRO) — Захищає GOT (Global Offset Table) з перезапису.

Збільшуючи ці захисти (зазвичайне за замовчуванням) піднімає план для експлуатації, але не замінює захищену кодування.

Аналіз коду та статистичний аналіз

Аналіз людини, що поєднує в собі автоматизований статичний аналіз, може зловити проблеми з переповненням буферів на початку. Інтеграція цих у процес розробки.

  • Manual code review — Дивитися для використання небезпечних функцій, відсутніх розмірів перевірок, а також петель, які зафіксують за межі буфера.
  • Статистика інструмент — Інструменти, такі як , , , і виявляють потенційні переливи, використання небезпечних функцій, а також помилки в режимі off-by-one. Вони можуть працювати в трубопроводах CI.
  • ] — Використовуйте libFuzzer, AFL або інші нечітки для автоматичного тестування введення даних з несподіваними даними, які можуть викликати переливи.

Практичні приклади безпечного коду

Сейф Стрінг Копія з пов'язами Перевірка

#include <stdio.h>
#include <string.h>

int safe_string_copy(char *dest, size_t dest_size, const char *src) {
 if (!dest || !src || dest_size == 0) {
 return -1; // Invalid parameters
 }
 size_t src_len = strlen(src);
 if (src_len >= dest_size) {
 // Source too large; truncation or error
 // Option: copy what fits and null-terminate
 strncpy(dest, src, dest_size - 1);
 dest[dest_size - 1] = '\0';
 return 1; // Truncation occurred
 }
 strncpy(dest, src, dest_size);
 // strncpy fills remaining with null, so dest_size fits; no need to null-terminate if src shorter
 return 0; // Success, no truncation
}

Безпечне використання Integer для Buffer Size

Переповнення буфера також може призвести до цілих переливів при розмірах обчислень. Завжди перевірте арифметичне перед виділенням.

#include <stdlib.h>
#include <limits.h>
#include <errno.h>

void *safe_malloc_array(size_t nmemb, size_t size) {
 if (nmemb == 0 || size == 0) {
 return NULL; // Or handle zero-size allocation
 }
 if (nmemb > SIZE_MAX / size) {
 // Integer overflow would occur
 errno = ENOMEM;
 return NULL;
 }
 return malloc(nmemb * size);
}

Використання спринтер для форматованих струн

char log_message[256];
int ret = snprintf(log_message, sizeof(log_message),
 "User %s logged in from %s", username, ip_address);
if (ret < 0) {
 // Output error
} else if ((size_t)ret >= sizeof(log_message)) {
 // Truncation occurred; handle if needed
}

Додаткові кращі практики

  • Initialize buffers — Завжди нульовий іниціалізувати буфери, щоб уникнути витікання неініціалізованої пам'яті.
  • Потрібна рецидивація з непідіймною глибиною — Стейк перелив може статися з глибокої рецидивності; використання ітерації або обмеження глибини.
  • Використовувати — Допомагає компілятору оптимізувати і може зловити питання, хоча не безпосередньо запобігаючи переповненню.
  • Prefer -correctness] — Запобігає випадковому модифікації вхідних рядків і неухиленню.
  • — Не ігноруйте значення зворотнього зв’язку з функціями, такими як , , , і т.д.

Ресурси для подальшого навчання

Висновок

Запобігання переповнення буферів в C не є необов'язковим; це фундаментальна відповідальність будь-якого розробника, що працює з мовою. З розумінням механізмів переливів, замінюючи небезпечні функції з безпечні альтернативи, суворо валідуючи вводи і розміри, що дозволяють захистити компілятор, і використовуючи статичний аналіз і тестування, ви можете різко зменшити ризик цих вразливостей. Немає однієї техніки достатньо; захист в глибині - поєднує дисципліну кодування, компілятор прапори, зниження OS і ретельне тестування - забезпечує найсильніший захист. З цими практиками ви можете писати код Crabities, який є потужним і безпечним, здатний безпечно працювати в складних системах.