![]() |
|
|||
WebMoney: WMZ Z294115950220 WMR R409981405661 WME E134003968233 |
Visa 4274 3200 2453 6495 |
Три новых интерфейса "_pointer()" были добавлены в
SQLite 3.20.0 (2017-08-01): Вопросы и беспорядок быстро возникли в
списках рассылки
о цели этих новых интерфейсов, почему они были представлены и какую проблему
они решают. Это эссе пытается ответить на те вопросы. Иногда удобно для расширений SQLite сообщить значения не-SQL между
субкомпонентами или между расширением и применением. Некоторые примеры: В расширении FTS3
оператор MATCH
(который делает полнотекстовый поиск) должен сообщить детали соответствия
записям в функциях snippet(),
offsets() и
matchinfo(), чтобы те функции могли
преобразовать детали в вывод. Для добавления расширений в
FTS5, таких как новый токенизатор,
нужен указатель на объект "fts5_api". В расширении CARRAY
приложение должно сказать расширению местоположение множества языка C,
которое содержит данные для табличной функции,
которую осуществляет расширение. Традиционный способ сообщить эту информацию состоял в том, чтобы
преобразовать указатель языка C в BLOB или 64-битное целое число, затем
переместить тот BLOB или целое число через SQLite с использованием обычных
интерфейсов, таких как
sqlite3_bind_blob(),
sqlite3_result_blob(),
sqlite3_value_blob() или
эквиваленты целого числа. Раздача указателей, как будто они были целыми числами или BLOB,
работает хорошо в окружающей среде, где компоненты приложения
написаны нормально. Однако указатели как целые числа и BLOB
позволяют враждебному коду на SQL создавать недопустимые указатели,
которые могут выполнить вред. Например, первый аргумент snippet(), как
предполагается, является специальной колонкой таблицы FTS3, которая содержит
указатель на объект fts3cursor, который содержит информацию о текущем
соответствии полнотекстового поиска. Тот указатель был раньше передан как
BLOB. Например, если таблица FTS3 названа "t1" и имеет колонку "cx",
можно было бы написать: Но если хакер в состоянии управлять произвольным SQL, он мог бы
управлять немного отличающимся запросом: Поскольку указатель передается в столбце t1 таблицы t1 как BLOB
(в более старых версиях SQLite), такой запрос показал бы значение
указателя в шестнадцатеричном виде.
Нападавший мог тогда изменить тот указатель, чтобы попытаться получить
функцию snippet(), чтобы изменить память в некоторой другой части прикладного
адресного пространства вместо fts3cursor: Исторически, это не считали угрозой. Аргумент был то, что, если враждебный
агент в состоянии ввести произвольный код в SQL в приложение, то тот агент
уже находится в полном контроле над приложением, таким образом позволить
враждебному агенту подделать указатель, не значит дать
агенту новой способности. Для большинства случаев верно, что у потенциальных нападавших нет способа
ввести произвольный SQL, и таким образом, большая часть использования SQLite
неуязвима для нападения. Но есть некоторые существенные исключения. Интерфейс
WebSQL позволил любой веб-странице управлять произвольным SQL в браузере
для Chrome и Safari. Тем произвольным SQL, как предполагалось, управляли в
песочнице, где он не мог причинить вреда, даже если бы был взломан, но та
песочница оказалась менее безопасной, чем хотелось бы.
Весной 2017 года одна команда хакеров смогла взломать iMac, используя длинную
последовательность действий, одно из которых было повреждение
указателей, переданных как BLOB, функции snippet() FTS3 в SQLite
через интерфейс WebSQL в Safari. На Android, нам говорят, есть многие сервисы, которые будут вслепую
управлять произвольным SQL, который передается им ненадежными приложениями,
которые были загружены с сомнительных мест. Сервисы Android предполагают,
большую защиту SQL из неисследуемых источников.
У этого автора нет определенных примеров, но он услышал слухи,
что они существуют. Даже если бы все сервисы Android
более тщательно и правильно исследуют весь SQL, которым они управляют, было
бы трудно ревизовать их всех, чтобы проверить, что они безопасны.
Следовательно, люди с высокими требованиями к безопасности стремятся
гарантировать, что никакие действия невозможны, передавая произвольный
код на SQL. Система управления версиями
Fossil
(разработана и написана в целях поддержки развития SQLite) позволяет
доверяемым пользователям вводить произвольный SQL для создания заявки
на устранение неисправности. Этот SQL санирован, используя интерфейс
sqlite3_set_authorizer().
Но это пример потенциально враждебной способности агентов ввести
произвольный SQL в систему. Первая попытка преодолевания разрывов безопасности в прохождении указателя
состояла в том, чтобы препятствовать тому, чтобы значения
указателя были подделаны. Это было достигнуто при наличии подтипа
отправителя, добавленного к каждому указателю, используя
sqlite3_result_subtype(), проверки
подтипа, используя sqlite3_value_subtype()
и отклонения указателей, у которых был неправильный подтип.
С тех пор нет никакого способа добавить подтип к результату, используя чистый
SQL, это препятствует тому, чтобы указатели были подделаны, используя SQL.
Единственный способ послать указатель состоит в том, чтобы использовать код
на C. Если нападавший может установить подтип, то он также в состоянии
подделать указатель без помощи SQLite. Используя подтипы, чтобы определить действительные указатели предотвратили
проблемы с WebSQL. Но это оказалось неполным решением.
Использование подтипов на указателях предотвратило подделку указателя,
используя чистый SQL. Но подтипы не делают ничего, чтобы препятствовать тому,
чтобы нападавший читал значения указателей. Другими словами, подтипы на
значениях указателя предотвращают нападения, используя SQL-операторы: Параметр BLOB для snippet()
не имеет правильного подтипа, таким образом, функция snippet
игнорирует его, не вносит изменений ни в какие структуры данных и
безопасно возвращает NULL. Но использование подтипов не делает ничего, чтобы препятствовать
тому, чтобы прочитать значение указателя, используя код SQL: Какой вред мог выйти из этого, вы спрашиваете? Разработчики SQLite
(включая этого автора) задались этим вопросом.
Но тогда исследователи в области безопасности указали, что знание указателей
может помочь нападавшим обойти обороноспособность рандомизации адресного
пространства. Это называют "утечкой указателя". Утечка указателя не
самостоятельная уязвимость, но она может помочь нападавшему в эффективной
эксплуатации других слабых мест. Разрешение дополнительным компонентам передать частную информацию друг
другу надежно и не вводя утечки указателя требует новых интерфейсов: В SQL значение, созданное
sqlite3_bind_pointer() и
sqlite3_result_pointer(),
неотличимо от NULL. SQL-оператор, который пытается использовать функцию
hex(), чтобы прочитать значение
указателя, получит ответ NULL SQL. Единственный способ обнаружить, есть ли у
значения связанный указатель, состоит в том, чтобы использовать
интерфейс sqlite3_value_pointer()
с соответствующим типом строки T. Значения указателя, прочитанные
sqlite3_value_pointer(), не могут быть произведены чистым SQL.
Следовательно, для SQL невозможно подделать указатели. Значения указателя, произведенные
sqlite3_bind_pointer() и
sqlite3_result_pointer(),
не могут быть прочитаны чистым SQL. Следовательно, для SQL невозможно
пропустить значение указателей. Таким образом новый передающий указатель интерфейс, кажется, решает
все проблемы безопасности, связанные с передачей
указателя от одного расширения другому в SQLite.
"pointer type" в последнем параметре
sqlite3_bind_pointer(),
sqlite3_result_pointer() и
sqlite3_value_pointer()
используется, чтобы предотвратить перенаправление указателя, предназначенного
для одного расширения в другое расширение. Например, без использования типов
указателей, нападавший мог все еще получить доступ к информации об указателе
в системе, которая включает FTS3 и
CARRAY, через SQL: В примере выше указатель курсора FTS3, произведенный оператором MATCH,
посылают в табличную функцию carray() вместо ее намеченного получателя
snippet(). Функция carray() рассматривает указатель как указатель на
множество целых чисел и возвращает каждое целое число одно за другим, таким
образом пропуская содержание объекта курсора FTS3. Так как объект курсора
FTS3 содержит указатели на другие объекты, запрос выше был
бы утечкой указателя. Но запрос выше не работает благодаря типам указателей.
У указателя, произведенного оператором MATCH, есть тип "fts3cursor", но
carray() ожидает указатель типа "carray".
Поскольку тип указателя на
sqlite3_result_pointer()
не совпадает с типом для вызова
sqlite3_value_pointer(),
sqlite3_value_pointer() вернет
NULL в carray() и таким образом сигнализирует расширению CARRAY, что был
передан недопустимый указатель. Типы указателей это статические последовательности, которые идеально
должны быть строковыми литералами, включенными непосредственно в вызов
SQLite API, а не параметры, переданные от других функций. Внимание было
уделено использованию целочисленных значений как тип указателя, но
статические последовательности обеспечивают намного большее пространство
имен, которое уменьшает шанс случайных столкновений имени типа
между несвязанными расширениями. Под "статической последовательностью", мы имеем в виду
законченное нолем множество байтов, которое фиксируется и неизменно для срока
действия программы. Другими словами, последовательность типа указателя должна
быть строковой константой. Напротив, "динамическая последовательность
" является законченным нолем множеством байтов, которое хранится в
памяти, ассигнованной из кучи, и должно быть освобождено, чтобы избежать
утечки памяти. Не используйте динамические последовательности в качестве
последовательности типа указателя. Многие комментаторы выразили желание использовать динамические
последовательности для типа указателя, чтобы SQLite мог
автоматически освободить последовательность типа, когда закончит
использование. Этот дизайн отклонен по следующим причинам: Тип указателя не предназначается, чтобы быть гибким и динамичным.
Тип указателя предназначается, чтобы быть константой времени проектирования.
Запросы не должны синтезировать последовательности типа указателя во время
выполнения. Оказание поддержки для динамических последовательностей типа
указателя дало бы возможность неправильно использовать передающие указатель
интерфейсы, создавая синтезированные во время выполнения последовательности
типа указателя. Требование, чтобы последовательности типа указателя были
статичны, поощряет разработчиков делать правильную вещь, выбирая
зафиксированные имена типа указателя во время проектирования и кодируя те
имена как постоянные строки. Все строковые значения на уровне SQL в SQLite это динамические
последовательности. Требование, чтобы последовательности типа были статичны,
мешает создавать определенную применением функцию SQL, которая может
синтезировать указатель произвольного типа. Мы не хотим, чтобы пользователи
создали такие функции SQL, так как такие функции поставили бы под угрозу
безопасность системы. Таким образом требование использовать статические
последовательности помогает защитить целостность передающих указатель
интерфейсов от плохо разработанных функций SQL.
Статическое требование последовательности не идеальная защита, так как
искушенный программист может обойти это, а программа новичка может просто
создать утечку памяти. Но заявляя, что последовательность типа указателя
должна быть статичной, мы надеемся поощрить разработчиков, которые могли бы
иначе использовать динамическую последовательность для типа указателя,
думать более тщательно о проблеме и избежать проблемы безопасности. Наличие у SQLite возможности брать строковые
типы наложило бы исполнительные издержки на все запросы, даже те,
которые не используют передающие указатель интерфейсы. SQLite передает
значения как экземпляры объекта sqlite3_value.
У того объекта есть деструктор, который из-за того, что объекты
sqlite3_value используются для почти всего, часто вызывается.
Если он должен проверить, есть ли последовательность типа указателя, которая
должна быть освобождена, это является несколькими дополнительными циклами
CPU, которые должны быть потрачены на каждом вызове деструктора. Мы были бы
готовы понести расходы дополнительных циклов CPU, если бы прохождение
указателя было обычно используемой парадигмой программирования, но
прохождение указателя редко, и таким образом, кажется неблагоразумным
добавить нагрузку во время выполнения на миллиарды и миллиарды запросов,
которые не используют указатель, только для удобства нескольких
запросов, которые это делают. Если вы чувствуете, что вам нужны динамические последовательности
типа указателя в вашем запросе, это
является сильным индикатором, что вы неправильно используете передающий
указатель интерфейс. Ваше надлежащее использование может быть небезопасным.
Пожалуйста, заново продумайте свой дизайн. Определите, необходимо ли
действительно передавать указатели через SQL.
Или, возможно, используйте иной механизм, кроме передающих указатель
интерфейсов, описанных этой статьей. Последний параметр
sqlite3_bind_pointer() и
sqlite3_result_pointer() это
указатель на процедуру, используемую, чтобы избавиться от указателя P, как
только SQLite закончил работу с ним.
Этот указатель может быть NULL, в этом случае никакой деструктор
не вызывают. Когда параметр D не NULL, это означает, что значение
указателя передается SQLite. SQLite возьмет на себя ответственность за
освобождение ресурсов, связанных с указателем, когда это закончит
использовать указатель. Если параметр D = NULL, это означает, что
значение указателя остается вызывающему, и уже
ответственен за избавление от указателя. Обратите внимание на то, что функция деструктора
D для значения P указателя, а не для последовательности типа T.
Последовательность типа T должна быть статической
последовательностью с бесконечной жизнью. Если значение указателя передается в SQLite, обеспечивая
не-NULL параметр D в
sqlite3_bind_pointer() или
sqlite3_result_pointer(), значение
остается с SQLite, пока объект не разрушен. Нет никакого способа передать
значение из SQLite назад в приложение снова. Указатели, которые осуществляют передачу SQL NULL, используя интерфейсы
sqlite3_bind_pointer(),
sqlite3_result_pointer() и
sqlite3_value_pointer(),
переходные и эфемерные. Указатели никогда не пишутся в базу данных.
Указатели не переживут сортировку. Последний факт это то,
почему нет интерфейса sqlite3_column_pointer(), так как невозможно
предсказать, вставит ли планировщик запроса операцию сортировки до
возвращения значения от запроса, таким образом, было бы невозможно знать,
выживет ли значение указателя, вставленное в запрос
sqlite3_bind_pointer() или
sqlite3_result_pointer()
в набору результатов. Значения указателя должны идти
непосредственно от их производителя их потребителю без промежуточных
операторов или функций. Любое преобразование значений
указателя уничтожает указатель и преобразовывает значение
в обычный NULL SQL. Ключевые выводы из этого: Интернет все более и более враждебное место.
Разработчики должны предположить, что нападавшие найдут способ выполнить
произвольный SQL. Запросы должны быть разработаны, чтобы препятствовать
тому, чтобы выполнение произвольного SQL возросло в
более серьезное деяние. Несколько расширений SQLite извлекают выгоду
из передачи указателей: Указатели никогда не должны
обмениваться, кодируя их как некоторый другой тип данных SQL, такой как
целые числа или BLOB. Вместо этого используйте интерфейсы, разработанные,
чтобы облегчить безопасное прохождение указателя:
sqlite3_bind_pointer(),
sqlite3_result_pointer() и
sqlite3_value_pointer(). Использование прохождения указателя это продвинутая техника, которая
должна использоваться нечасто и осторожно. Прохождение указателя не должно
использоваться случайно или небрежно. Последовательность "pointer type", которая является последним
параметром к каждому из передающих указатель интерфейсов, должна быть
отличным, специализированным строковым литералом, который появляется
непосредственно в вызове API. Тип указателя не должен быть параметром,
переданным от высокоуровневой функции.
Choose any three.
1. Обзор
2.
Краткая история
2.1.
Повышение уровня угрозы
SELECT snippet(t1) FROM t1 WHERE cx MATCH $pattern;
SELECT hex(t1) FROM t1 WHERE cx MATCH $pattern;
SELECT snippet(x'6092310100000000') FROM t1 WHERE cx MATCH $pattern;
2.2.
Предотвращение подделанных указателей
2.3. Утечки указателя
SELECT snippet(x'6092310100000000') FROM t1 WHERE cx MATCH $pattern;
SELECT hex(t1) FROM t1 WHERE cx MATCH $pattern;
3.
Новые передающие указатель интерфейсы
3.1. Типы указателей
SELECT ca.value FROM t1, carray(t1,10) AS ca WHERE cx MATCH $pattern
3.1.1.
Типы указателей: статические последовательности
3.2. Деструктор
4.
Ограничения на использование указателя
5. Резюме