Статьи

Использование rlimit (и почему вы должны)

Я просмотрел несколько старых заметок и наткнулся на напоминание о setrlimit (2) .

Это системный вызов C, который позволяет приложению указывать ограничения ресурсов по ряду важных параметров:

  • RLIMIT_AS — максимальный размер виртуальной памяти процесса (адресного пространства) в байтах.
  • RLIMIT_CORE — максимальный размер файла ядра.
  • RLIMIT_CPU — ограничение времени процессора в секундах.
  • RLIMIT_DATA — максимальный размер сегмента данных процесса (инициализированные данные, неинициализированные данные и куча).
  • RLIMIT_FSIZE — максимальный размер файлов, которые может создать процесс.
  • RLIMIT_MEMLOCK — максимальное количество байтов памяти, которое может быть заблокировано в ОЗУ.
  • RLIMIT_MSGQUEUE — определяет ограничение на количество байтов, которое может быть выделено для очередей сообщений POSIX для реального идентификатора пользователя вызывающего процесса.
  • RLIMIT_NICE — Определяет потолок, до которого значение nice процесса может быть увеличено с помощью setpriority (2) или nice (2).
  • RLIMIT_NOFILE — указывает значение, которое больше максимального номера дескриптора файла, который может быть открыт этим процессом.
  • RLIMIT_NPROC — максимальное количество процессов (или, точнее, в Linux, потоков), которое может быть создано для реального идентификатора пользователя вызывающего процесса.
  • RLIMIT_RSS — определяет ограничение (в страницах) резидентного набора процесса (количество виртуальных страниц, резидентных в ОЗУ).
  • RLIMIT_RTPRIO — Определяет верхний предел приоритета в реальном времени, который может быть установлен для этого процесса с использованием sched_setscheduler (2) и sched_setparam (2).
  • RLIMIT_RTTIMEзадает ограничение (в микросекундах) на количество процессорного времени, которое процесс, запланированный в соответствии с политикой планирования в реальном времени, может потреблять без выполнения системного вызова блокировки.
  • RLIMIT_SIGPENDING — определяет ограничение на количество сигналов, которые могут быть поставлены в очередь для реального идентификатора пользователя вызывающего процесса.
  • RLIMIT_STACK — максимальный размер стека процесса в байтах.

Ограничения для всех программ указаны в файлах конфигурации (/etc/security/limits.conf и /etc/security/limits.d) или могут быть установлены в отдельной оболочке и ее процессах с помощью функции оболочки ‘ulimit’. В Linux текущие ограничения ресурсов для процесса отображаются в / proc / [pid] / limit.

Пределы также могут быть установлены программно, через setrlimit (2). Любой процесс может дать себе более ограничительные ограничения. Любой привилегированный процесс (запущенный от имени пользователя root или с правильной способностью) может дать себе более допустимые ограничения.

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

Почему мы заботимся?

Безопасность в глубине.

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

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

Вот несколько конкретных примеров:

Во-первых, установка RLIMIT_NPROC в ноль означает, что процесс не может выполнить / запустить новый процесс — злоумышленник не может выполнить произвольный код в качестве текущего пользователя. (Примечание: страницы руководства предполагают, что это может ограничить общее количество процессов для пользователя, а не только в этом процессе и его дочерних элементах. Это должно быть перепроверено.) Это также предотвращает более тонкую атаку, когда процесс многократно разветвляется до желаемый PID получен. PID должны быть уникальными, но, по-видимому, некоторые ядра теперь поддерживают большее пространство PID, чем традиционный pid_t. Это означает, что устаревшие системные вызовы могут быть неоднозначными.

Во-вторых, установка RLIMIT_AS , RLIMIT_DATA и RLIMIT_MEMLOCK в разумные значения не позволяет процессу принудительно переключать систему, ограничивая доступную память.

В-третьих, задание разумного значения для RLIMIT_CORE (или полное отключение дампов ядра) исторически использовалось для предотвращения атак типа «отказ в обслуживании» путем заполнения диска дампами ядра. Сегодня дампы ядра часто отключаются, чтобы гарантировать, что конфиденциальная информация, такая как ключи шифрования, непреднамеренно не будет записана на диск, где злоумышленник сможет впоследствии получить их. Конфиденциальная информация также должна быть записана в memlock (), чтобы предотвратить ее запись на диск подкачки.

Как насчет Java?

Влияет ли это на Java?

Да.

Стандартный загрузчик классов поддерживает открытый «дескриптор файла» для каждого загруженного класса. Это могут быть тысячи открытых файловых дескрипторов для серверов приложений. Я видел реальные сбои, которые в конечном итоге были отслежены до достижения предела RLIMIT_NOFILE .

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

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

Третий — написать простое приложение запуска, которое устанавливает более высокий предел, прежде чем выполнять exec () для запуска сервера приложений или IDE разработчика. Это гарантирует, что только авторизованные приложения получают дополнительные ресурсы.

(Java SecurityManager также можно использовать для ограничения использования ресурсов, но это выходит за рамки этого обсуждения.)

Образец кода

Наконец, пример кода со страницы руководства prlimit. Версия setrlimit похожа.

01
02
03
04
05
06
07
08
09
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
#define _GNU_SOURCE
#define _FILE_OFFSET_BITS 64
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/resource.h>
 
#define errExit(msg)    do { perror(msg);  exit(EXIT_FAILURE); } while (0)
 
int
main(int argc, char *argv[])
{
    struct rlimit old, new;
    struct rlimit *newp;
    pid_t pid;
 
    if (!(argc == 2 || argc == 4)) {
        fprintf(stderr, "Usage: %s  [<new-soft-limit> <new-hard-limit>]\n", argv[0]);
        exit(EXIT_FAILURE);
     }
 
     pid = atoi(argv[1]);        /* PID of target process */
 
     newp = NULL;
     if (argc == 4) {
         new.rlim_cur = atoi(argv[2]);
         new.rlim_max = atoi(argv[3]);
         newp = ≠w;
     }
 
     /* Set CPU time limit of target process; retrieve and display previous limit */
     if (prlimit(pid, RLIMIT_CPU, newp, &old) == -1)
         errExit("prlimit-1");
         printf("Previous limits: soft=%lld; hard=%lld\n",
             (long long) old.rlim_cur, (long long) old.rlim_max);
 
    /* Retrieve and display new CPU time limit */
    if (prlimit(pid, RLIMIT_CPU, NULL, &old) == -1)
           errExit("prlimit-2");
           printf("New limits: soft=%lld; hard=%lld\n",
              (long long) old.rlim_cur, (long long) old.rlim_max);
 
    exit(EXIT_FAILURE);
}

Использование на практике

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

В других случаях труднее определить хорошие пределы, но есть три подхода.

Во-первых, сосредоточиться на том, что важно. Например, многие приложения знают, что они никогда не должны запускать подпроцесс, поэтому RLIMIT_NPROC можно установить на ноль. (Опять же, после проверки того, что это предел процессов в рамках текущего процесса, а не всех процессов для пользователя.) Они знают, что никогда не нужно открывать больше, чем несколько дополнительных файлов, поэтому можно установить RLIMIT_NOFILE , чтобы разрешить несколько больше открытых файлов, но не больше. Даже эти скромные ограничения могут иметь большое значение для ограничения ущерба.

Второе — просто выбрать какое-то большое значение, которое, как вы уверены, будет достаточно для ограничения использования памяти или процессора. Возможно, 100 МБ на порядок больше, но на порядок меньше, чем было раньше. Этот подход может быть особенно полезен для подпроцессов в архитектуре начальник / рабочий, где объем ресурсов, требуемых для каждого отдельного работника, может быть оценен правильно.

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

  • Запустите программу с правами суперпользователя root, но немедленно измените действующего пользователя на непривилегированного пользователя.
  • Установите верхний жесткий предел и низкий мягкий предел.
  • Проверьте, установлен ли мягкий предел на каждый системный вызов. (Вы должны уже проверить на ошибки.)
  • При попадании с мягким ограничением смените действующего пользователя на root, увеличьте мягкое ограничение, восстановите первоначального действующего пользователя и повторите операцию.
  • Регистрируйте его каждый раз, когда вам нужно увеличить мягкое ограничение. Вариант — опрос внешнего процесса в файле / proc / [pid] / limit.

С хорошими функциональными и приемочными тестами у вас должно быть четкое представление о ресурсах, необходимых для программы. Вы по-прежнему хотите быть щедрыми с конечными ограничениями ресурсов, но это должно дать вам хорошую оценку «порядка» для того, что вам нужно, например, 10 МБ против 2 ГБ.

На последнем замечании: дисковые квоты

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

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

Ссылка: Использование rlimit (и почему вы должны) от нашего партнера JCG Беара Джайлса в блоге Invariant Properties .