Переполнение буфера при записи в массив с как исправить

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
#include <iostream>
using namespace std;
void sortik(int*& mass, int w, int &e, int &sr)
{
    for (int i = 1; i < w; i++)
    {
        int a = mass[i];
        int b = i - 1;
        while ((++sr) && (mass[b] > a) && (b >= 0))
        {
            mass[b + 1] = mass[b];
            --b;
            e++;
        }
        mass[b + 1] = a;
    }
}
 
void insertik(int *&mass, int &s, int inc)
{
    int *mass1 = new int[s+1];
    for (int i = 0; i < s; i++)
    {
        mass1[i] = mass[i];
    }
    mass1[s] = inc;
    s++;
    delete[]mass;
    mass = mass1;
}
 
 
void deletik(int *&mass, int& q, int inc)
{
    int* mass1 = new int[q - 1];
    if (inc == 1)
    {
        for (int i = 1; i < q; i++)
        {
            mass1[i-1] = mass[i];
        }
        delete[]mass;
        mass = mass1;
        q--;
        return;
        
    }
    if (inc == q)
    {
        for (int i = 0; i < q-1; i++)
        {
            mass1[i] = mass[i];
        }
        delete[]mass;
        mass = mass1;
        q--;
        return;
    }
    else
    {
        for (int i = 0; i < inc-1; i++)
        {
            mass1[i] = mass[i];
        }
        for (int i = inc; i < q; i++)
        {
            mass1[i-1] = mass[i];
        }
        delete[]mass;
        mass = mass1;
        q--;
        return;
    }
}
 
 
int main()
{
    setlocale(LC_ALL, "RUS");
    int n;
    int value = 0;
    int sravn = 0;
    int k;
    int cop = 0;
    cout << "Введите количество элементов массива:";
    cin >> n;
    if (n == 0)
    {
        cout << "Ошибка, в следующей попытке будьте внимательнее" << endl;
        return 0;
    }
    int *mas = new int [n];
    cout << "Введите элементы массива: ";
    for (int i = 0; i < n; i++)
    {
        cin >> mas[i];
    }
    int dop = 0;
    while(true)
    {
        sortik(mas, n, value, sravn);
        cout << "Отсортированный массив: ";
        for (int i = 0; i < n; i++)
        {
            cout << mas[i] << " ";
        }
        cout << endl << "Количество перестановок:" << value << endl;
        cout << "Количество сравнений:" << sravn << endl;
        cout << "Введите число - нужный вам номер числа сортированного массива: ";
        while (!(cin >> k) || (cin.peek() != 'n') || (k > n))
        {
            cin.clear();
            while (cin.get() != 'n');
            cout << "Ошибка, повторите ввод" << endl << "Нужный вам номер числа сортированного массива: ";
        }
        cout <<"Элемент под этим номером равен: "<< mas[k - 1] << endl;
        cout << "Если хотите добавить элемент, введите 0" << endl;
        cout << "Если хотите удалить элемент, введите 1, иначе введите любое другое число кроме 0 и 1, чтобы программа завершилась" << endl;
        cin >> dop;
            if (dop == 0)
            {
                cout << "Введите элемент, который хотите добавить"<<endl;
                cin >> cop;
                insertik(mas, n, cop);
                sravn = 0;
                value = 0;
                continue;
            }
            if (dop == 1)
            {
                cout << "Введите номер элемента, который хотите удалить" << endl;
                while (!(cin >> cop) || (cin.peek() != 'n') || (cop > n))
                {
                    cin.clear();
                    while (cin.get() != 'n');
                    cout << "Ошибка, повторите ввод" << endl << "Нужный вам номер элемента, который хотите удалить: ";
                }
                deletik(mas, n, cop);
                sravn = 0;
                value = 0;
                if (n == 0)
                {
                    cout << "Вы удилили весь массив" << endl;
                    cout << "Спасибо за использование программы, ждём вас снова" << endl;
                    delete [] mas;
                    return 0;
                }
                continue;;
            }
            else
            {
                cout << "Спасибо за использование программы, ждём вас снова" << endl;
                delete[] mas;
                return 0;
            }
    }
}

Функция считывает матрицу из файла в динамический массив, сразу же выводит её и отправляет в main, предварительно узнав размеры этой матрицы из другой функции (плюс разные проверки существования файла и прочее, но не суть). Сама программа и данная функция работают безупречно, но анализ кода в Visual Studio выдаёт вот такое вот предупреждение:

Предупреждение C6386. Переполнение буфера при записи в «matrix»:
доступный для записи объем равен «strings*sizeof(int *)» байт,
однако записать можно только «16» байт.

Так вот, мне интересно почему он на это ругается и стоит ли обращать внимание?

P.S. Анализом кода занялся совсем недавно, к примеру узнал про нулевой указатель и в функции уже это дело подправил.

Сам код:

int **GetMatrixFile(const char filename[], const int strings, const int columns)
{
    FILE *file;
    int **matrix;

    fopen_s(&file, filename, "r");

    if (file != NULL)
    {
        printf("Матрица %c(%d x %d):n",
            filename[0], strings, columns);

        matrix = (int**)malloc(strings * sizeof(int*));
        if (matrix != NULL)
        {
            for (int i = 0; i < strings; ++i)
            {
                printf("|");

                matrix[i] = (int*)malloc(columns * sizeof(int));
                if (matrix[i] != NULL)
                {
                    for (int j = 0; j < columns; ++j)
                    {
                        fscanf_s(file, "%d", &matrix[i][j]);
                        printf("%3d ", matrix[i][j]);
                    }
                }
                else exit(EXIT_FAILURE);

                printf("|n");
            }
        }
        else exit(EXIT_FAILURE);
        fclose(file);
    }
    else exit(EXIT_FAILURE);

    return matrix;
}

I am trying to turn a vector of char* into an array of char pointer but I get this annoying error and I can’t figure out what I’m doing wrong.

char** Parse::toCommand(std::vector<char*> command) {
    char** array = new char* [command.size()];
    for (int i = 0; i < command.size(); i++) {
        array[i] = command[i];
    }

    return array;
}

I get this warning which causes my program to not run.

 Buffer overrun while writing to 'array':  the writable size is 'command.public: unsigned int __thiscall std::vector<char *,class std::allocator<char *> >::size(void)const ()*4' bytes, but '8' bytes might be written.

the char* is actually a c string of course.

The strings in the vector are pieces of a string that was cut up using strtok_s. I got rid of the Null at the end of every string by converting each to a c str using string::copy() to get a non constant c string and using the constructor of std::string to get a regular string. I then popped the back to rid myself of the null.

My end goal is I want to have an array of c strings so that I can pass it to execvp()

for (int i = 0; i < exes.size(); i++) {  //Separate each executable and argument list into vector of char* and push that to 2d vector of char*

        char* arg = exes[i]; //arg = the entire string executable and arguments
        std::vector <char*> argV;

        char* place = NULL;

        ptr3 = strtok_s(arg, " ", &place);

        while (ptr3 != NULL) {

            if (*ptr3 == '"') {//if beginning of remaining char* begins with ", push char*
                std::string temp;
                temp.push_back(*ptr3);
                ptr3 = strtok_s(NULL, """, &place);
                temp.append(ptr3);
                temp.pop_back();
                temp.push_back('"');
                char* cstr = new char[temp.size()];
                temp.copy(cstr, temp.size(), 0);
                argV.push_back(cstr);
            }
            else if (*ptr3 == '#') {
                break;
            }
            else {
                std::string temp(ptr3);
                temp.pop_back();
                char* cstr = new char[temp.size()];
                temp.copy(cstr, temp.size(), 0);
                argV.push_back(cstr);
            }
            ptr3 = strtok_s(NULL, " ", &place);
        }

        argV.push_back(NULL);
        args.push_back(argV);
    }

    for (int i = 0; i < args.size(); i++) {
        char** command = this->toCommand(args[i]);
        commands[i] = new COM(command);
    }

argV is a vector<vector<char*>> and

Переполнение буфера

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

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

  • scanf("%s", buf) — нет проверки размера буфера
  • strcpy(dst, src) — нет проверки размера буфера
  • strcat(dst, src) — нет проверки размера буфера
  • gets(str) — нет проверки размера буфера
  • memcpy(dst, src, n) — проверку размера dst нужно делать вручную.

И еще многие другие, преимущественно работающие со строками, функции.

Эти функции доставляли и продолжают доставлять проблемы. Некоторые компиляторы (msvc) по умолчанию откажутся собирать ваш код, если увидят одну из них. Другие будут менее заботливыми и, возможно, выдадут предупреждение. По крайней мере про функцию gets уж точно. Если с другими функциями у программиста есть возможность уберечься (проверка до вызова; у scanf можно указать размер в ограничение строке), то с gets — без вариантов.

Для большинства старых небезопасных сишных функций сейчас есть «безопасные» аналоги с размерами буферов. Часть из них не стандартизирована, часть стандартизирована. Все это породило огромное количество костылей с макроподстановками для работы со всем этим зоопарком. Но сейчас не об этом.


Проверки размеров — дополнительная работа. Генерировать под них инструкции — замедлять программу. Тем более программист мог все проверить сам. Так что в C/С++ обращение за границы массива, хоть на запись, хоть на чтение — влечет неопределенное поведение. И дыры в безопасности могут зарастать различными спецэффектами.

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

Но иногда начинается веселье.

const int N = 10;
int elements[N];

bool contains(int x) {
    for (int i = 0; i <= N; ++i) {
        if (x == elements[i]) {
            return true;
        }
    }
    return false;
}

int main() {
    for (int i = 0; i < N; ++i) {
        std::cin >> elements[i];
    }
    return contains(5);
}

Эта программа, собранная gcc c оптимизациями, всегда «найдет» пятерку в массиве. Независимо от того какие числа будут введены.
Причем никаких предупреждений ни clang, ни gcc не производят.

Происходит такой спецэффект из следующих соображений:

  1. Компиляторы вольны считать, что UB в программах не бывает
  2.  for (int i = 0; i <= N; ++i) {
         if (x == elements[i]) {
             return true;
         }
     }

    В этом цикле будет обращение за границы массива, а значит UB.

  3. Но, так как UB не бывает, до N+1 итерации дело дойти не должно
  4. Значит, мы выйдем из цикла по return true
  5. А значит вся функция contains — это один return true. Оптимизировано!

Или вот конечный цикл становится бесконечным:

const int N = 10;
int main() {
    int decade[N];
    for (int k = 0; k <= N; ++k) {
        printf("k is %dn",k);
        decade[k] = -1;
    }
}

И фокус здесь не менее хитрый:

  1. decade[k] = -1; Обращение к элементу массива должно быть без UB. А значит k < N
  2. Раз k < N, то условие продолжения цикла k <= N — всегда истинно. Проверять его не надо. Оптимизировано!

В этих примерах, конечно, сразу же должен броситься в глаза <= в заголовках циклов. Но и с более привычным < тоже можно изобрести себе проблемы. Константа N, например, может быть не связана с размером массива. И все, приехали.


В дружелюбных и безопасных языках вы получите ошибку во время выполнения. Панику или исключение. В C++ же все надо проверять, проверять и еще раз проверять самим:

  • Не использовать отдельно висящие константы при проверке размеров. Лучше std::size() или метод size()
  • Писать меньше сырых циклов со счетчиками. Предпочтительнее range-based-for или стандартные алгоритмы из #include <algorithm>
  • Не использовать operator[], когда не критична производительность. Безопаснее метод at() контейнера, проверяющий границы.

Полезные ссылки

  1. https://blog.rapid7.com/2019/02/19/stack-based-buffer-overflow-attacks-what-you-need-to-know/
  2. https://dhavalkapil.com/blogs/Buffer-Overflow-Exploit/

Я пытаюсь реализовать сортировку слиянием в C, когда натолкнулся на что-то интересное, поднятое [Analyze -> Run Code Analysis] в Visual Studio 2015.

Код выглядит следующим образом:

void MergeSort_r(int A[], int n)
{
// A = {1, 3, 2}
// n = 3
int rightCount;
int* R;

if ( n < 2 ) return;

// version 1: rightCount = 2
rightCount = n - (n/2);

// version 2: rightCount = 2
rightCount = n - 1;

R = ( int* ) malloc( rightCount * sizeof( int ) );

if ( R ) {
for ( int i = 0; i < rightCount; i++ ) {
R[i] = A[i];
}

free( R );
}

}

Хотя обе версии rightCount по существу оцениваются как 2, в первой версии я получаю предупреждение:

«Переполнение буфера при записи в ‘R’: размер записи равен ‘(unsigned int) rightCount * sizeof (int)’ байтов, но может быть записано 8 байтов.»

Есть идеи, почему это так? Будем рады услышать ваши ответы.

2

Решение

Набор инструментов для анализа кода в Visual C ++ не всегда предлагает лучшие предупреждения. Он пытается дать вам лучший набор предупреждений, чтобы исправить некоторые потенциальные проблемы / ошибки, которые могут появиться во время выполнения. У вас есть несколько вариантов:

  • Отключите данное предупреждение вокруг кода, используя #pragma директивы.
  • Используйте конструкции C ++: new, make_unique и т.п.
  • (Не рекомендуется) это полностью игнорировать предупреждение и двигаться дальше.

В идеале вы всегда должны использовать новые примитивы smart pointers, такие как unique_ptr, shared_ptr и т.д. Они не только выделяют память для вас, но и освобождают от любого исключения, выброшенного в стек вызовов. Вам не нужно вводить * полностью!

auto buffer = make_unique<int[]>(10); // 10 integers

3

Другие решения

Ваш код в порядке, и инструменты (особенно анализаторы) имеют свои недостатки — иногда они генерируют ложные срабатывания. Это один из них. Кстати, я проверил ваш код на MSVS2015, и он не дает мне никаких предупреждений.

3

Понравилась статья? Поделить с друзьями:
  • Как найти скорость машины 4 класс
  • Как составить должностную инструкцию по охране труда
  • Как найти 2000000 рублей
  • Как можно исправить вальгусную стопу у ребенка
  • Как правильно составить объявление по электрике