Очистить память string c
большинство результатов поиска я нашел в основном заявил, что очистка содержимого String невозможно (поскольку строка неизменяема) и SecureString должно быть используемый.
поэтому я придумал свое собственное решение (используя небезопасный код) ниже. Тестирование показывает, что решения работают, но я все еще не уверен, что с решением что-то не так? Есть ли лучшие?
EDIT: из-за комментариев о GC перемещение строки вокруг до clearString вызывается: как насчет следующего фрагмента?
ваша проблема в том, что струны могут двигаться. Если GC запускается, он может переместить содержимое в новое место, но он не будет обнулять старое. Если вы обнулили строку, о которой идет речь, у вас нет гарантии, что ее копия не существует в другом месте в памяти.
изменить: вот ваша проблема с обновлением:
помимо стандартного ответа "вы вступаете в небезопасную территорию", который, я надеюсь, объясняет себя, рассмотрим следующее:
среда CLR не гарантирует, что в любой заданной точке существует только один экземпляр строки, и она не гарантирует, что строки будут собраны в мусор. Если бы я сделал следующее:--2-->
что это? (Предположим, что я не использую строковые литералы, и это вместо входов из некоторой среды какой-то)
строка создается с содержимым "somestring". Еще одна строка создается с содержимым "конфиденциальная информация", а еще одна строка создается с содержимым"somestringsensitive info". Очищается только последняя строка: "конфиденциальная информация" - нет. Это может быть или не может быть немедленно мусор собирается.
даже если вы заботитесь о том, чтобы всегда очищать любую строку с конфиденциальной информацией, среда CLR все равно не гарантирует, что только одна экземпляр строки существует.
edit: Что касается вашего редактирования, просто закрепление строки сразу может иметь желаемый эффект - нет необходимости копировать строку в другое место или что-то еще. Вам нужно сделать это сразу после получения указанной строки, и есть еще другие проблемы безопасности, о которых нужно беспокоиться. Вы не можете гарантировать, что, например, источник строки не имеет копии в своей памяти, без четкого понимания источник и как именно он все делает.
вы также не сможете мутировать эту строку по очевидным причинам (если мутированная строка не имеет точно такого же размера, что и строка), и вам нужно быть очень осторожным, чтобы ничто из того, что вы делаете, не могло наступить на память, которая не является частью этой строки.
кроме того, если вы передадите его другим функциям, которые вы не писали сами, он может быть скопирован или не скопирован этой функцией.
невозможно сказать, сколько функций CLR и non-CLR проходит ваша строка, прежде чем она достигнет вашей функции, где вы пытаетесь ее очистить. Эти функции (управляемые и неуправляемые) могут создавать копии строки по различным причинам (возможно, несколько копий).
вы не можете знать все эти места и очистить их все так реалистично, вы не можете гарантировать, что ваш пароль будет очищен из памяти. Вы должны использовать SecureString вместо этого, но вам нужно понять, что вышесказанное все еще применяется: в какой-то момент в вашей программе вы получите пароль, и вам придется иметь его в памяти (даже если только на короткий срок, пока вы перемещаете его в безопасную строку). Это означает, что ваша строка все равно будет проходить через цепочки вызовов функций, которые вы не контролируете.
Если вы действительно можете использовать SecureString , и вы готовы написать небезопасный код, тогда вы можете написать свой собственный простой класс строк, который использует неуправляемую память и гарантирует, что вся память обнуляется перед освобождением.
однако вы никогда не сможете гарантировать, что ваши данные безопасны, так как вы никогда не имеете полного контроля над ними. Например, вирус, встроенный достаточно глубоко, может прочитать эту память во время работы программы, и это также возможность того, что процесс завершается, и в этом случае код деструктора не запускается, оставляя данные в нераспределенной памяти, которая может быть выделена другому процессу, и она будет по-прежнему первоначально содержать ваши конфиденциальные данные; кто-то может легко использовать инструмент, такой как visual studio для мониторинга памяти отлаженного процесса, или написать программу, которая выделяет память и выполняет поиск конфиденциальных данных.
В предыдущей главе уже обсуждалось, что локальные переменные кладутся на стек и существую до тех пор, пока мы не вышли из функции. С одной стороны, это позволяет автоматически очищать память, с другой стороны, существует необходимость в переменных, время жизни которых мы можем контролировать самостоятельно. Кроме того, нам необходимо динамическое выделение памяти, когда размер используемого пространства заранее не известен. Для этого используется выделение памяти на куче. Недостатков у такого подхода два: во-первых, память необходимо вручную очищать, во-вторых, выдеение памяти – достаточно дорогостоящая операция.
Для выделения памяти на куче в си используется функция malloc (memory allocation) из библиотеки stdlib.h
Функция выделяет size байтов памяти и возвращает указатель на неё. Если память выделить не удалось, то функция возвращает NULL. Так как malloc возвращает указатель типа void, то его необходимо явно приводить к нужному нам типу. Например, создадим указатель, после этого выделим память размером в 100 байт.
После того, как мы поработали с памятью, необходимо освободить память функцией free.
Используя указатель, можно работать с выделенной памятью как с массивом. Пример: пользователь вводит число – размер массива, создаём массив этого размера и заполняем его квадратами чисел по порядку. После этого выводим и удаляем массив.
Здесь (int *) – приведение типов. Пишем такой же тип, как и у указателя.
size * sizeof(int) – сколько байт выделить. sizeof(int) – размер одного элемента массива.
После этого работаем с указателем точно также, как и с массивом. В конце не забываем удалять выделенную память.
Теперь представим на рисунке, что у нас происходило. Пусть мы ввели число 5.
Функция malloc выделила память на куче по определённому адресу, после чего вернула его. Теперь указатель p хранит этот адрес и может им пользоваться для работы. В принципе, он может пользоваться и любым другим адресом.
Когда функция malloc "выделяет память", то она резервирует место на куче и возвращает адрес этого участка. У нас будет гарантия, что компьютер не отдаст нашу память кому-то ещё. Когда мы вызываем функцию free, то мы освобождаем память, то есть говорим компьютеру, что эта память может быть использована кем-то другим. Он может использовать нашу память, а может и нет, но теперь у нас уже нет гарантии, что эта память наша. При этом сама переменная не зануляется, она продолжает хранить адрес, которым ранее пользовалась.
Это очень похоже на съём номера в отеле. Мы получаем дубликат ключа от номера, живём в нём, а потом сдаём комнату обратно. Но дубликат ключа у нас остаётся. Всегда можно зайти в этот номер, но в нём уже кто-то может жить. Так что наша обязанность – удалить дубликат.
Иногда думают, что происходит "создание" или "удаление" памяти. На самом деле происходит только перераспределение ресурсов.
Освобождение памяти с помощью free
Т еперь рассмотри, как происходит освобождение памяти. Переменная указатель хранит адрес области памяти, начиная с которого она может им пользоваться. Однако, она не хранит размера этой области. Откуда тогда функция free знает, сколько памяти необходимо освободить?
- 1. Можно создать карту, в которой будет храниться размер выделенного участка. Каждый раз при освобождении памяти компьютер будет обращаться к этим данным и получать нужную информацию.
- 2. Второе решение более распространено. Информация о размере хранится на куче до самих данных. Таким образом, при выделении памяти резервируется места больше и туда записывается информация о выделенном участке. При освобождении памяти функция free "подсматривает", сколько памяти необходимо удалить.
Работа с двумерными и многомерными массивами
Д ля динамического создания двумерного массива сначала необходимо создать массив указателей, после чего каждому из элементов этого массива присвоить адрес нового массива.
Для удаления массива необходимо повторить операцию в обратном порядке - удалить сначала подмассивы, а потом и сам массив указателей.
- 1. Создавать массивы "неправильной формы", то есть массив строк, каждая из которых имеет свой размер.
- 2. Работать по отдельности с каждой строкой массива: освобождать память или изменять размер строки.
Создадим "треугольный" массив и заполним его значениями
Чтобы создать трёхмерный массив, по аналогии, необходимо сначала определить указатель на указатель на указатель, после чего выделить память под массив указателей на указатель, после чего проинициализировать каждый из массивов и т.д.
calloc
Ф ункция calloc выделяет n объектов размером m и заполняет их нулями. Обычно она используется для выделения памяти под массивы. Синтаксис
realloc
Е щё одна важная функция – realloc (re-allocation). Она позволяет изменить размер ранее выделенной памяти и получает в качестве аргументов старый указатель и новый размер памяти в байтах:
Функция realloc может как использовать ранее выделенный участок памяти, так и новый. При этом не важно, меньше или больше новый размер – менеджер памяти сам решает, где выделять память.
Пример – пользователь вводит слова. Для начала выделяем под слова массив размером 10. Если пользователь ввёл больше слов, то изменяем его размер, чтобы хватило места. Когда пользователь вводит слово end, прекращаем ввод и выводим на печать все слова.
Хочу обратить внимание, что мы при выделении памяти пишем sizeof(char*), потому что размер указателя на char не равен одному байту, как размер переменной типа char.
Ошибки при выделении памяти
1. Бывает ситуация, при которой память не может быть выделена. В этом случае функция malloc (и calloc) возвращает NULL. Поэтому, перед выделением памяти необходимо обнулить указатель, а после выделения проверить, не равен ли он NULL. Так же ведёт себя и realloc. Когда мы используем функцию free проверять на NULL нет необходимости, так как согласно документации free(NULL) не производит никаких действий. Применительно к последнему примеру:
Хотелось бы добавить, что ошибки выделения памяти могут случиться, и просто выходить из приложения и выкидывать ошибку плохо. Решение зависит от ситуации. Например, если не хватает памяти, то можно подождать некоторое время и после этого опять попытаться выделить память, или использовать для временного хранения файл и переместить туда часть объектов. Или выполнить очистку, сократив используемую память и удалив ненужные объекты.
2. Изменение указателя, который хранит адрес выделенной области памяти. Как уже упоминалось выше, в выделенной области хранятся данные об объекте - его размер. При удалении free получает эту информацию. Однако, если мы изменили указатель, то удаление приведёт к ошибке, например
Таким образом, если указатель хранит адрес, то его не нужно изменять. Для работы лучше создать дополнительную переменную указатель, с которой работать дальше.
3. Использование освобождённой области. Почему это работает в си, описано выше. Эта ошибка выливается в другую – так называемые висячие указатели (dangling pointers или wild pointers). Вы удаляете объект, но при этом забываете изменить значение указателя на NULL. В итоге, он хранит адрес области памяти, которой уже нельзя воспользоваться, при этом проверить, валидная эта область или нет, у нас нет возможности.
Эта программа отработает и выведет мусор, или не мусор, или не выведет. Поведение не определено.
Если же мы напишем
то программа выкинет исключение. Это определённо лучше, чем неопределённое поведение. Если вы освобождаете память и используете указатель в дальнейшем, то обязательно обнулите его.
4. Освобождение освобождённой памяти. Пример
Здесь дважды вызывается free для переменной a. При этом, переменная a продолжает хранить адрес, который может далее быть передан кому-нибудь для использования. Решение здесь такое же как и раньше - обнулить указатель явно после удаления:
5. Одновременная работа с двумя указателями на одну область памяти. Пусть, например, у нас два указателя p1 и p2. Если под первый указатель была выделена память, то второй указатель может запросто скомпрометировать эту область:
Рассмотрим код ещё раз.
Теперь оба указателя хранят один адрес.
А вот здесь происходит непредвиденное. Мы решили выделить под p2 новый участок памяти. realloc гарантирует сохранение контента, но вот сам указатель p1 может перестать быть валидным. Есть разные ситуации. Во-первых, вызов malloc мог выделить много памяти, часть которой не используется. После вызова ничего не поменяется и p1 продолжит оставаться валидным. Если же потребовалось перемещение объекта, то p1 может указывать на невалидный адрес (именно это с большой вероятностью и произойдёт в нашем случае). Тогда p1 выведет мусор (или же произойдёт ошибка, если p1 полезет в недоступную память), в то время как p2 выведет старое содержимое p1. В этом случае поведение не определено.
Два указателя на одну область памяти это вообще-то не ошибка. Бывают ситуации, когда без них не обойтись. Но это очередное минное поле для программиста.
Различные аргументы realloc и malloc.
При вызове функции malloc, realloc и calloc с нулевым размером поведение не определено. Это значит, что может быть возвращён как NULL, так и реальный адрес. Им можно пользоваться, но к нему нельзя применять операцию разадресации.
Вызов realloc(NULL, size_t) эквиваленте вызову malloc(size_t).
Однако, вызов realloc(NULL, 0) не эквивалентен вызову malloc(0) :) Понимайте это, как хотите.
Примеры
1. Простое скользящее среднее равно среднему арифметическому функции за период n. Пусть у нас имеется ряд измерений значения функции. Часто эти измерения из-за погрешности "плавают" или на них присутствуют высокочастотные колебания. Мы хотим сгладить ряд, для того, чтобы избавиться от этих помех, или для того, чтобы выявить общий тренд. Самый простой способ: взять n элементов ряда и получить их среднее арифметическое. n в данном случае - это период простого скользящего среднего. Так как мы берём n элементов для нахождения среднего, то в результирующем массиве будет на n чисел меньше.
Это простой пример. Большая его часть связана со считыванием данных, вычисление среднего всего в девяти строчках.
2. Сортировка двумерного массива. Самый простой способ сортировки - перевести двумерный массив MxN в одномерный размером M*N, после чего отсортировать одномерный массив, а затем заполнить двумерный массив отсортированными данными. Чтобы не тратить место под новый массив, мы поступим по-другому: если проходить по всем элементам массива k от 0 до M*N, то индексы текущего элемента можно найти следующим образом:
j = k / N;
i = k - j*M;
Заполним массив случайными числами и отсортируем
3. Бином Ньютона. Создадим треугольную матрицу и заполним биномиальными коэффициентами
Если Вы желаете изучать этот материал с преподавателем, советую обратиться к репетитору по информатике
При записи различных значений в эту строку, память занимаемая строкой может только увеличиваться, но не уменьшаться. Меня это сильно напрягает, так как раход памяти моей программы из за этого увеличивается в 10 раз. Я думаю, что должен быть метод освобождающий память, но найти я его пока не смог.
> При записи различных значений в эту строку, память занимаемая строкой
> может только увеличиваться, но не уменьшаться. Меня это сильно
> напрягает, так как раход памяти моей программы из за этого увеличивается
> в 10 раз. Я думаю, что должен быть метод освобождающий память, но найти
> я его пока не смог.
Posted via ActualForum NNTP Server 1.4
Вообще, если подумать головой, то можно
САМ ВЕКТОР УДАЛИТЬ.
И новый создать.
Posted via ActualForum NNTP Server 1.4
> Решение стандартное: напиши свой класс строки.
Posted via ActualForum NNTP Server 1.4
вы не обольщайтесь, данный способ конечно значительно лучше delete/memset (убивать! убивать!), но отнюдь не 100% корректен.
чем хуже предложенные варианты?
Пользоваться swap, значит переписывать много красивого кода в менее красивую форму. Лень и жалко.
При использовании "string *ps = new string;" или auto_ptr красота кода почти не нарушается. Но это может ощутимо снизить производительность (лишний обьект в хипе, который надо часто уничтожать/создавать).
Дописывание STL, собственный класс строки - это неэстетично.
при возникновении исключении в конструкторе string в опреаторе new и обработке его где-то в коде выше непременно будет вызван деструктор исходного string, который после явного вызова деструктора находица в ХЗ каком состоянии.
на мой вкус это некорректно.
> при возникновении исключении в конструкторе string в опреаторе new и
> обработке его где-то в коде выше непременно будет вызван деструктор
> исходного string, который после явного вызова деструктора находица в ХЗ
> каком состоянии.
>
> на мой вкус это некорректно.
Явный вызов деструктора -- 100% корректный код. И если при повторном
вызове деструктора деструктор будет работать неправильно, это проблемы
этого класса.
Как хранить конфиденциальные данные (например, пароли) в std::string ?
У меня есть приложение, которое запрашивает у пользователя пароль и передает его на нижестоящий сервер во время установки соединения. Я хочу надежно очистить значение пароля после установления соединения.
Если я сохраню пароль в виде массива char * , я могу использовать такие API, как SecureZeroMemory , чтобы избавиться от конфиденциальных данных из памяти процесса. Тем не менее, я хочу избегать массивов символов в моем коде и ищу что-то похожее для std::string ?
4 ответа
На основе ответа, данного здесь , я написал распределитель надежно обнулить память.
Однако оказывается, что в зависимости от того, как реализован std::string , возможно, что распределитель даже не вызывается для малых ценности. Например, в моем коде deallocate даже не вызывается для строки bar (в Visual Studio).
Тогда ответ таков: мы не можем использовать std :: string для хранения конфиденциальных данных. Конечно, у нас есть возможность написать новый класс, который обрабатывает сценарий использования, но мне было особенно интересно использовать std::string , как определено.
Спасибо всем за помощь!
std :: string основан на символе *. Где-то позади вся динамическая магия как символ *. Поэтому, когда вы говорите, что не хотите использовать char * в своем коде, вы все еще используете char *, он просто на заднем плане с кучей другого мусора, сложенного поверх него.
Я не слишком разбираюсь в памяти процесса, но вы всегда можете перебирать каждый символ (после того как вы зашифровали и сохранили пароль в БД?) и установить для него другое значение.
Также есть std :: basic_string, но я не уверен, какая помощь вам поможет.
Для потомков я однажды решил проигнорировать этот совет и в любом случае использовать std :: string и написал метод zero (), используя c_str () (и отбрасывающий константу) и volatile. Если я был осторожен и не вызывал перераспределение /перемещение содержимого, и я вручную вызывал zero () там, где мне было нужно, чтобы все было чистым, казалось, все работало правильно. Увы, я обнаружил еще один серьезный недостаток: сложный способ: std :: string также может быть объектом, на который ссылаются . взрывая память с помощью c_str () (или памяти, на которую указывает ссылочный объект), будет неосознанно взрывать другой объект .
Здравствуйте, Аноним, Вы писали:
А>Добрый день.
А>Подскажите плиз как очистить строку std::string в памяти.
А>Т.е. не просто erase() а гарантированное стирание всей строки.
А>например для стирания пароля.
Ну можно попробовать такой трюк:
Здравствуйте, Bell, Вы писали:
B>Здравствуйте, Аноним, Вы писали:
А>>Добрый день.
А>>Подскажите плиз как очистить строку std::string в памяти.
А>>Т.е. не просто erase() а гарантированное стирание всей строки.
А>>например для стирания пароля.
B>Ну можно попробовать такой трюк:
B>
Нисагласен. Насколько я понял, надо не память освободить, а старое значение затереть. Правда, непонятно, зачем.
Правильно работающая программа — просто частный случай Undefined Behavior Здравствуйте, Bell, Вы писали:B>Ну можно попробовать такой трюк:
B>
Насколько я понимаю, это вариант не затрет строку, а просто переместит ее во временный объект и никакой гарантии что она будет стерта нет. или я ошибаюсь?
Здравствуйте, _Winnie, Вы писали:
_W>Нисагласен. Насколько я понял, надо не память освободить, а старое значение затереть. Правда, непонятно, зачем.
Нужно гарантированно затереть данные(пароль).
_W>
Использую нечто похожее.
Хотелось бы что-то более элегантное, а желательно и быстрое.
Здравствуйте, Аноним, Вы писали:
А>Здравствуйте, _Winnie, Вы писали:
_W>>Нисагласен. Насколько я понял, надо не память освободить, а старое значение затереть. Правда, непонятно, зачем.
А>Нужно гарантированно затереть данные(пароль).
_W>>
А>Использую нечто похожее.
А>Хотелось бы что-то более элегантное, а желательно и быстрое.
Здравствуйте, Аноним, Вы писали:
А>Здравствуйте, Bell, Вы писали:
B>>Ну можно попробовать такой трюк:
B>>
А>Насколько я понимаю, это вариант не затрет строку, а просто переместит ее во временный объект и никакой гарантии что она будет стерта нет. или я ошибаюсь?
Переместит во временный объект, у которого будет тут же будет вызван деструктор.
Господа, такой трюк со swap описан у господина Мейерса. Насколько Вы круты, чтобы сомневатся в компететности Мейерс?
Здравствуйте, LuciferMoscow, Вы писали:
LM>Господа, такой трюк со swap описан у господина Мейерса. Насколько Вы круты, чтобы сомневатся в компететности Мейерс?
Тут, яя так понял, стоит проблема защита информации. Т.е. даже если ты отдашь используемую память операционной системем через free, delete etc, никакой гарантии, что память по этому адресу не будет содержать свое последнее значение бесконечно долго, у тебя нет. И, соответственно, можно это значение оттуда каким-то образом вытащить. Наиболее распространенный способ обломать потенциального взломщика — записать по этому адресу какой-нибудь мусор, а уже потом освободить память.
Здравствуйте, LuciferMoscow, Вы писали:
LM>Здравствуйте, Аноним, Вы писали:
А>>Здравствуйте, Bell, Вы писали:
B>>>Ну можно попробовать такой трюк:
B>>>
А>>Насколько я понимаю, это вариант не затрет строку, а просто переместит ее во временный объект и никакой гарантии что она будет стерта нет. или я ошибаюсь?
LM>Переместит во временный объект, у которого будет тут же будет вызван деструктор.
LM>Господа, такой трюк со swap описан у господина Мейерса. Насколько Вы круты, чтобы сомневатся в компететности Мейерс?
В принципе господа правы . Память старой строки конечно освободится, но содержимое этой памяти сохранится какое-то время, чем теоретически могут поспользоваться злобные хацкеры.
Здравствуйте, Garrrrr, Вы писали:G>Здравствуйте, LuciferMoscow, Вы писали:
LM>>Господа, такой трюк со swap описан у господина Мейерса. Насколько Вы круты, чтобы сомневатся в компететности Мейерс?
G>Тут, яя так понял, стоит проблема защита информации. Т.е. даже если ты отдашь используемую память операционной системем через free, delete etc, никакой гарантии, что память по этому адресу не будет содержать свое последнее значение бесконечно долго, у тебя нет. И, соответственно, можно это значение оттуда каким-то образом вытащить. Наиболее распространенный способ обломать потенциального взломщика — записать по этому адресу какой-нибудь мусор, а уже потом освободить память.
Согласен. Можно добавить такую перестраховку. Но что мешает злоумышленнику полезть по этому адресу на Х тысячных секунд раньше?
Здравствуйте, Garrrrr, Вы писали:
А>>Использую нечто похожее.
А>>Хотелось бы что-то более элегантное, а желательно и быстрое.
G>
Здравствуйте, LuciferMoscow, Вы писали:
LM>Согласен. Можно добавить такую перестраховку. Но что мешает злоумышленнику полезть по этому адресу на Х тысячных секунд раньше?
Можно уменьшить его шансы
Теоретически, информацию защищать бессмысленно — всегда найдется метод взлома
Здравствуйте, Аноним, Вы писали:
G>>
А>О! Спасибо!
чтоб враг не догадался что пароль вообще был
Здравствуйте, Garrrrr, Вы писали:
_W>>>Нисагласен. Насколько я понял, надо не память освободить, а старое значение затереть. Правда, непонятно, зачем.
А>>Нужно гарантированно затереть данные(пароль).
_W>>>
А>>Использую нечто похожее.
А>>Хотелось бы что-то более элегантное, а желательно и быстрое.
G>
Ни тот ни другой вариант не годятся.
Где гарантии того, что с момента загрузки до момента затирания страница памяти со строкой не выгружалась в своп-файл? А поскольку такой гарантии у вас нет, то все эти варианты "очистки" не дают никакой реальной защищённости (они не вычистят копию пароля из pagefile.sys), поэтому толку от них не больше, чем от трюка со swap`ом.
Поэтому, исходя из выше сказанного, std::basic_string не годится для хранения секретных данных, которые необходимо стирать после завершения их использования. Для хранения таких данных придётся использовать прямое распределение виртуальной памяти (VirtualAlloc) и механизм блокирования страниц в памяти (функция VirtualLock). Только так вы сможете запретить выгрузку страниц в своп-файл и следовательно при последующей (достаточно перед освобождением памяти забить её нулями) чистке гарантировать полное стирание данных.
Здравствуйте, Аноним, Вы писали:
А>Здравствуйте, Garrrrr, Вы писали:
А>>>Использую нечто похожее.
А>>>Хотелось бы что-то более элегантное, а желательно и быстрое.
G>>
А>О! Спасибо!
Читайте также: