RussianLDP Рейтинг@Mail.ru
WebMoney: 
WMZ Z294115950220 
WMR R409981405661 
WME E134003968233 
Visa 
4274 3200 2453 6495 

Small. Fast. Reliable.
Choose any three.

1. Обзор

Три новых интерфейса "_pointer()" были добавлены в SQLite 3.20.0 (2017-08-01):

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

2. Краткая история

Иногда удобно для расширений 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() или эквиваленты целого числа.

2.1. Повышение уровня угрозы

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

Например, первый аргумент snippet(), как предполагается, является специальной колонкой таблицы FTS3, которая содержит указатель на объект fts3cursor, который содержит информацию о текущем соответствии полнотекстового поиска. Тот указатель был раньше передан как BLOB. Например, если таблица FTS3 названа "t1" и имеет колонку "cx", можно было бы написать:

SELECT snippet(t1) FROM t1 WHERE cx MATCH $pattern;

Но если хакер в состоянии управлять произвольным SQL, он мог бы управлять немного отличающимся запросом:

SELECT hex(t1) FROM t1 WHERE cx MATCH $pattern;

Поскольку указатель передается в столбце t1 таблицы t1 как BLOB (в более старых версиях SQLite), такой запрос показал бы значение указателя в шестнадцатеричном виде. Нападавший мог тогда изменить тот указатель, чтобы попытаться получить функцию snippet(), чтобы изменить память в некоторой другой части прикладного адресного пространства вместо fts3cursor:

SELECT snippet(x'6092310100000000') FROM t1 WHERE cx MATCH $pattern;

Исторически, это не считали угрозой. Аргумент был то, что, если враждебный агент в состоянии ввести произвольный код в 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 в систему.

2.2. Предотвращение подделанных указателей

Первая попытка преодолевания разрывов безопасности в прохождении указателя состояла в том, чтобы препятствовать тому, чтобы значения указателя были подделаны. Это было достигнуто при наличии подтипа отправителя, добавленного к каждому указателю, используя sqlite3_result_subtype(), проверки подтипа, используя sqlite3_value_subtype() и отклонения указателей, у которых был неправильный подтип. С тех пор нет никакого способа добавить подтип к результату, используя чистый SQL, это препятствует тому, чтобы указатели были подделаны, используя SQL. Единственный способ послать указатель состоит в том, чтобы использовать код на C. Если нападавший может установить подтип, то он также в состоянии подделать указатель без помощи SQLite.

Используя подтипы, чтобы определить действительные указатели предотвратили проблемы с WebSQL. Но это оказалось неполным решением.

2.3. Утечки указателя

Использование подтипов на указателях предотвратило подделку указателя, используя чистый SQL. Но подтипы не делают ничего, чтобы препятствовать тому, чтобы нападавший читал значения указателей. Другими словами, подтипы на значениях указателя предотвращают нападения, используя SQL-операторы:

SELECT snippet(x'6092310100000000') FROM t1 WHERE cx MATCH $pattern;

Параметр BLOB для snippet() не имеет правильного подтипа, таким образом, функция snippet игнорирует его, не вносит изменений ни в какие структуры данных и безопасно возвращает NULL.

Но использование подтипов не делает ничего, чтобы препятствовать тому, чтобы прочитать значение указателя, используя код SQL:

SELECT hex(t1) FROM t1 WHERE cx MATCH $pattern;

Какой вред мог выйти из этого, вы спрашиваете? Разработчики SQLite (включая этого автора) задались этим вопросом. Но тогда исследователи в области безопасности указали, что знание указателей может помочь нападавшим обойти обороноспособность рандомизации адресного пространства. Это называют "утечкой указателя". Утечка указателя не самостоятельная уязвимость, но она может помочь нападавшему в эффективной эксплуатации других слабых мест.

3. Новые передающие указатель интерфейсы

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

  • sqlite3_bind_pointer(S,I,P,T,D) → Призывает указатель P типа T к I-ому параметру подготовленного запроса S. D это опциональный деструктор для P.
  • sqlite3_result_pointer(C,P,T,D) → Возвращает указатель P типа T как аргумент функции C. D это опциональный деструктор для P.
  • sqlite3_value_pointer(V,T) → Возвращает указатель типа T, связанный со значением V, если V не имеет никакого связанного указателя, или если указатель на V имеет тип, отличающийся от T, то возвращают NULL.

В 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.

3.1. Типы указателей

"pointer type" в последнем параметре sqlite3_bind_pointer(), sqlite3_result_pointer() и sqlite3_value_pointer() используется, чтобы предотвратить перенаправление указателя, предназначенного для одного расширения в другое расширение. Например, без использования типов указателей, нападавший мог все еще получить доступ к информации об указателе в системе, которая включает FTS3 и CARRAY, через SQL:

SELECT ca.value FROM t1, carray(t1,10) AS ca WHERE cx MATCH $pattern

В примере выше указатель курсора FTS3, произведенный оператором MATCH, посылают в табличную функцию carray() вместо ее намеченного получателя snippet(). Функция carray() рассматривает указатель как указатель на множество целых чисел и возвращает каждое целое число одно за другим, таким образом пропуская содержание объекта курсора FTS3. Так как объект курсора FTS3 содержит указатели на другие объекты, запрос выше был бы утечкой указателя.

Но запрос выше не работает благодаря типам указателей. У указателя, произведенного оператором MATCH, есть тип "fts3cursor", но carray() ожидает указатель типа "carray". Поскольку тип указателя на sqlite3_result_pointer() не совпадает с типом для вызова sqlite3_value_pointer(), sqlite3_value_pointer() вернет NULL в carray() и таким образом сигнализирует расширению CARRAY, что был передан недопустимый указатель.

3.1.1. Типы указателей: статические последовательности

Типы указателей это статические последовательности, которые идеально должны быть строковыми литералами, включенными непосредственно в вызов SQLite API, а не параметры, переданные от других функций. Внимание было уделено использованию целочисленных значений как тип указателя, но статические последовательности обеспечивают намного большее пространство имен, которое уменьшает шанс случайных столкновений имени типа между несвязанными расширениями.

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

Многие комментаторы выразили желание использовать динамические последовательности для типа указателя, чтобы SQLite мог автоматически освободить последовательность типа, когда закончит использование. Этот дизайн отклонен по следующим причинам:

  1. Тип указателя не предназначается, чтобы быть гибким и динамичным. Тип указателя предназначается, чтобы быть константой времени проектирования. Запросы не должны синтезировать последовательности типа указателя во время выполнения. Оказание поддержки для динамических последовательностей типа указателя дало бы возможность неправильно использовать передающие указатель интерфейсы, создавая синтезированные во время выполнения последовательности типа указателя. Требование, чтобы последовательности типа указателя были статичны, поощряет разработчиков делать правильную вещь, выбирая зафиксированные имена типа указателя во время проектирования и кодируя те имена как постоянные строки.

  2. Все строковые значения на уровне SQL в SQLite это динамические последовательности. Требование, чтобы последовательности типа были статичны, мешает создавать определенную применением функцию SQL, которая может синтезировать указатель произвольного типа. Мы не хотим, чтобы пользователи создали такие функции SQL, так как такие функции поставили бы под угрозу безопасность системы. Таким образом требование использовать статические последовательности помогает защитить целостность передающих указатель интерфейсов от плохо разработанных функций SQL. Статическое требование последовательности не идеальная защита, так как искушенный программист может обойти это, а программа новичка может просто создать утечку памяти. Но заявляя, что последовательность типа указателя должна быть статичной, мы надеемся поощрить разработчиков, которые могли бы иначе использовать динамическую последовательность для типа указателя, думать более тщательно о проблеме и избежать проблемы безопасности.

  3. Наличие у SQLite возможности брать строковые типы наложило бы исполнительные издержки на все запросы, даже те, которые не используют передающие указатель интерфейсы. SQLite передает значения как экземпляры объекта sqlite3_value. У того объекта есть деструктор, который из-за того, что объекты sqlite3_value используются для почти всего, часто вызывается. Если он должен проверить, есть ли последовательность типа указателя, которая должна быть освобождена, это является несколькими дополнительными циклами CPU, которые должны быть потрачены на каждом вызове деструктора. Мы были бы готовы понести расходы дополнительных циклов CPU, если бы прохождение указателя было обычно используемой парадигмой программирования, но прохождение указателя редко, и таким образом, кажется неблагоразумным добавить нагрузку во время выполнения на миллиарды и миллиарды запросов, которые не используют указатель, только для удобства нескольких запросов, которые это делают.

Если вы чувствуете, что вам нужны динамические последовательности типа указателя в вашем запросе, это является сильным индикатором, что вы неправильно используете передающий указатель интерфейс. Ваше надлежащее использование может быть небезопасным. Пожалуйста, заново продумайте свой дизайн. Определите, необходимо ли действительно передавать указатели через SQL. Или, возможно, используйте иной механизм, кроме передающих указатель интерфейсов, описанных этой статьей.

3.2. Деструктор

Последний параметр 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 назад в приложение снова.

4. Ограничения на использование указателя

Указатели, которые осуществляют передачу SQL NULL, используя интерфейсы sqlite3_bind_pointer(), sqlite3_result_pointer() и sqlite3_value_pointer(), переходные и эфемерные. Указатели никогда не пишутся в базу данных. Указатели не переживут сортировку. Последний факт это то, почему нет интерфейса sqlite3_column_pointer(), так как невозможно предсказать, вставит ли планировщик запроса операцию сортировки до возвращения значения от запроса, таким образом, было бы невозможно знать, выживет ли значение указателя, вставленное в запрос sqlite3_bind_pointer() или sqlite3_result_pointer() в набору результатов.

Значения указателя должны идти непосредственно от их производителя их потребителю без промежуточных операторов или функций. Любое преобразование значений указателя уничтожает указатель и преобразовывает значение в обычный NULL SQL.

5. Резюме

Ключевые выводы из этого:

  1. Интернет все более и более враждебное место. Разработчики должны предположить, что нападавшие найдут способ выполнить произвольный SQL. Запросы должны быть разработаны, чтобы препятствовать тому, чтобы выполнение произвольного SQL возросло в более серьезное деяние.

  2. Несколько расширений SQLite извлекают выгоду из передачи указателей:

    • Оператор FTS3 MATCH передает указатели в snippet(), offsets() и matchinfo().
    • Табличная функция carray должна принять указатель на множество значений языка C от приложения.
    • Расширению remember() нужен указатель на целочисленную переменную языка C, в которой можно хранить значение, которое это передает.
    • Приложения должны получить указатель на объект "fts5_api", чтобы добавить расширения, такие как свой токенизатор, в расширение FTS5.
  3. Указатели никогда не должны обмениваться, кодируя их как некоторый другой тип данных SQL, такой как целые числа или BLOB. Вместо этого используйте интерфейсы, разработанные, чтобы облегчить безопасное прохождение указателя: sqlite3_bind_pointer(), sqlite3_result_pointer() и sqlite3_value_pointer().

  4. Использование прохождения указателя это продвинутая техника, которая должна использоваться нечасто и осторожно. Прохождение указателя не должно использоваться случайно или небрежно.

  5. Последовательность "pointer type", которая является последним параметром к каждому из передающих указатель интерфейсов, должна быть отличным, специализированным строковым литералом, который появляется непосредственно в вызове API. Тип указателя не должен быть параметром, переданным от высокоуровневой функции.