Иногда у меня спрашивают о ошибках MySQL, (например таких), которые
могут привести к обрушиванию mysql-сервера пользователем с обычными
привелегиями. Потом звучит вопрос: "Что же делать в таких случаях? Как
защититься от подобных ситуаций?"
Мой ответ "Сядьте и расслабтесь :)"
Действительно, существует очень много ситауций, в которых сервер
падает или не отвечает на запросы. Причем ситуации эти существуют для
любых версий MySQL и их можно воспроизвести, обладая базовыми
привелегиями для доступа к sql-серверу.
Мы часто помогаем людям исправлять ошибки в их приложениях,
используюших MySQL (но положа руку на сердце можно сказать - в
большинстве случаев они сами являются причиной некорректной работы
сервера) и ощибки эти очевидны.
По-моему, девиз безопасности MySQL должна выглядеть так: если у вас
полностью закрыт доступ к серверу, вы действительно защищены. Для
некоторых видов атак не нужен валидный аккаунт, но такие ошибки
довольно быстро исправляются. В тот момент, когда вы даете кому-то
аккаунт вы перестаете полностью контролировать ситуацию. И значит, что
защищенность сервера становится немного ниже.
Такое положение вещей будет сохраняться все время, пока MySQL
использует Глобальное Управление Ресурсами, и вы реально не можете
контролировать количество ресурсов, доступных вашим пользователям.
Вы можете возразить, что это, в принципе, не катастрофа. Конечно, но
сервер можно еще перегрузить и сделать его недоступным. Или заставить
MySQL использовать всю доступную память, в результате чего сервер
будет просто убит операционной системой. На 32-битных системах
провести подобные трюки гораздо легче.
Дам вам несколько советов:
Временные таблицы вы можете использовать запросы с производными
таблицами и создавать в памяти неограниченное количество временных
таблиц с неограниченным размером.
Таблицы в памяти Если вы можете создать в памяти одну таблицу, значит
вы можете создать любое количество. Хотя размер таблицы ограничен
директивой max_heap_table_size, общий размер таблиц не ограничен
никак. Вы можете создавать таблицы как TEMPORARY и их не будет в
файловой системе.
MyISAM Sort Buffer - обычно его размер устанавливают довольно большим,
но он может одноврменно работать только с несколькими таблицами. А что
будет, если пользователь использует все его дозволеные 100 подключений
для инструкции ALTER для 100 разных таблиц? Можно искусственно
ограничить его занижением значения параметра myisam_sort_buffer_size,
но это отразится на производительности.
Количество подготовленных инструкций (Prepared Statements) - к счастью
теперь есть лимит на максимальное количество подготовленных инструкций
(параметр max_stmt_count), который задается сервером. Это лучше, чем
было раньше, когда можно было заставить сервер использовать всю
доступную память, просто забыв закрыть полготовленные инструкции.
Однако ограничения для пользователя отстутствуют и один пользователь
может исчерпать весь лимит, заблокировав досутп к использованию
подготовленных инструкций для остальных. Кроме того, не все инструкции
занимают одинаковое количество памяти и подготовка сложных инструкций
может отнять бoльшую часть ресурсов. Решить проблему можно, ограничив
использование подготовленных инструкций и установив параметр
max_prepared_stmt_count в очень низкое значение.
Подготовленные инструкции и BLOB-данные - если вы хотите получить
память, занятую одной подготовленной инструкцией, вы можете создать
инструкцию с тысячей меток (placeholders) и посылать данные каждой из
них, используя вызов mysql_stmt_send_long_data. Буферы сервера будут
принимать данные до тех пор, пока инструкция не будет выполнена.
Утечки кэша таблиц Innodb - InnoDB никогда не ограничивает размер
внутренних таблиц в кеше (словарь данных). Так что открывая большие
таблицы и работая с ними вы можете исчерпать всю доступную память.
ОБычно размер 4-8К на таблицу, но сложные таблицы могут занимать и
больше. В основном это проблема небольших серверов.
Кэш Слитых таблиц - кэш таблиц обычно распределен и каждая запись
обычно соответствует не более, чем паре файловых дескрипторов. Но не в
случае Слитых таблиц - создание и доступ к нескольким слитым таблицам
с, например, тысячей подтаблиц не лучшим образом скажется на вашем
сервере. Тоже самое относится и к Разделенным талицам из MySQL 5.1
Место на диске - для MyISAM-таблиц хостинг-провайдеры обычно
используют дисковые квоты. Вы можете использовать похожий подход при
помощи innodb_file_per_table. Однако вы не можете контролировать рост
системных таблиц InnoDB, которые используются для хранения данных об
отменах и которые могут увеличиваться с каждой открытой транзакцией и
делать множество обновлений. Или просто сохранять транзакцию открытой
и позволять другим пользователям делать обновления - InnoDB может
только очистить данные после старых транзакций, нуждающихся в
мгновенном коммите. Вы можете решить этот вопрос путем уничтожения
слишком старых транзакций, но более верным решением будет установление
некоего лимита на объем хранимой информации о отменах. Еще один
неплохой способ - это использовать запросы с большими временными
таблицами или сортировкой файлов. Даже если временные таблицы будут
храниться на другом разделе, при его переполнении другие пользователи
не смогут нормально работать с сервером.
Хранимые процедуры - сколько памяти можно выделить для хранимой
процедуры? Можно создать 1000 переменных в хранимой процедуре и
зарезервировать для каждой их них по 1М памяти. Я не проводил
целенаправленных экспериментов, но думаю, что жесткой политики
выделения памяти для хранимых процедур нет.
Указатели в хранимых процедурах - указатели в хранимых процедурах
реализованы в виде временных таблиц. Поэтому используя большое
количество указателей можно заполнить доступный объем оперативной
памяти.
Рекурсия в хранимых процедурах - На самом деле отличается от
"классического" представления рекурсии. Это всего лишь вызовы одной
процедуры из другой. Которые так же требуют выделения памяти и, что
важно, размещаются в стеке. Есть некоторые способы для защиты от
переполнения стека, но они не могут гарантировать 100%-ного
результата.
Переменные на стороне сервера - каждый сервер имеет ограничение в виде
директивы max_allowed_packet (1M по умолчанию). Но, по-видимому, нет
никаких ограничений на количество создаваемых перменных.
Разбор деревьев - внутренне представление запросов в MySQL выглядит
как разбор дерева, которое зависит от размера запроса, который, в свою
очередь, контролируется директивой max_allowed_packet. Но некоторые
способы оптимизирования MySQL (такие как equity propagation и range
expansion) могут привести к критическим ошибкам при анализе дерева.
Для нескольких тривиальных случаев такое поведение было исправлено,
но вызывает сомнения, что эти исправления актуальны для всех подобных
ситуаций.
Сессионные переменные - нет никаких ограничений на использование
переменных непривилегированным пользователем, которому разрешено
выполнять запросы без ограничения доступа к ресурсам.
Блокировка хоста - сервер может заблокировать хост клиента из-за
большого количества неудачных соединений. Этого можно избежать,
выставив большое значение для параметра max_connect_errors, но будьте
осторожны: это снизит устойчивость к брутфорсу.
Взаимные исключения - как InnoDB, так и MyISAM имеют "хотспоты" с
несколькими соединениями. Используя их можно существенно замедлить
работу сервера.
Перегрузка - у MySQL нет достаточного контроля за использованием
ресурсов и вы можете "положить" сервер просто выполняя тяжелые
запросы. Существующие ограничения не могут полностью контролировать
потребление ресурсов пользователем, поскольку не имеют механизма
определения "тяжести" запроса. Тяжелые запросы могут сильно нагрузить
систему ввода/вывода и заполнить кэш ОС, что заметно снизит скорость
работы сервера и запросы других пользователей будут выполняться на
порядок медленнее.
Некоторые из вышеизложенных способов были испробованы на реальных
приложений, некоторые являются лишь предположением о возможном
поведении сервера в случае критических ситуаций.
Как вы видите, MySQL-сервер отнюдь не выглядит "пуленепробиваемым",
если кто-то намеренно будет пытаться его обрушить. Дело в том, что
большинство существующих ограничений (таких как max_heap_table_size
или max_prepared_stmt_count) реализованы для защиты от типичных ошибок
при разработке приложений, а отнюдб не от запланированной атаки.
Примечание: я рассмотрел лишь некоторые из объектов сервера, т.е.
снижение производительности при некорректной работе с каждым из них.
Существует ряд глобальных квот, которые влияют на работу сервера в
целом - вы не можете применить их для конкретного пользователя или
соединения.
P.S.: вы можете подумать: "как же это все возможно, если существуют
тысячи хостинг-провайдеров, предоставляющих доступ к своим серверам
БД?" Да, некоторым везт и их пользователи не создают проблем для
корректной работы MySQL, но большинству приходится "вручную" постоянно
контролировать и ограничивать пользователей, мешающих нормальному
функционированию. Не говоря о компаниях, предоставляющих вирутальный
хостинг - там, как правило, используются старые версии ПО, имеющие
куда больше проблем.
P.P.S.: ничего из вышеописанного не является "новостью" для команды
разработчиков MySQL. Эта заметка - лишь попытка осветить некоторые
аспекты безопасности и обеспечения корректной работы MySQL-сервера.