В попытке избежать конфликта стека атак на мы попытались установить ограничение на размер стека с setrlimit(RLIMIT_STACK)
примерно до 2 МБ.
Это ограничение подходит для внутренних нужд нашей программы, но затем мы заметили, что попытки exec()
внешних программ начали терпеть неудачу на некоторых системах с этим новым ограничением. Одна система, которую мы исследовали с помощью приведенной ниже тестовой программы, по-видимому, имеет минимальный размер стека для exec()
-программ чуть более 4 МБ.
Мой вопрос в том, как мы можем узнать безопасное минимальное значение размера стека в данной системе, чтобы exec()
не потерпел неудачу?
Мы не хотим просто повышать это значение до тех пор, пока не перестанут сбои на всех системах, с которыми мы в настоящее время тестируем, поскольку это может привести к сбоям в будущем, поскольку программа переносится на более новые типы систем с более высокими минимальными требованиями.
Приведенная ниже тестовая программа написана в терминах system()
, но симптом более низкого уровня ошибка в системном вызове execl()
. В зависимости от хост-ОС, на которой вы тестируете, вы получаете либо errno == E2BIG
, либо segfault в вызываемой программе, когда вы предоставляете вызываемой программе слишком мало места в стеке для запуска.
Построить с помощью:
$ CFLAGS="-std=c99 -D_POSIX_C_SOURCE=200809" make stacklim
Этот вопрос косвенно связан с "Проверить условие ошибки E2BIG в exec", но наш фактический вопрос отличается: мы вас интересуют потенциальные проблемы с переносимостью, которые вызывает установка этого ограничения.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <unistd.h>
static enum try_code {
raise_minimum,
lower_maximum,
failed_to_launch
}
try_stack_limit(rlim_t try)
{
// Update the stack limit to the given value
struct rlimit x;
getrlimit(RLIMIT_STACK, &x);
static int first_time = 1;
if (first_time) {
first_time = 0;
printf("The default stack limit here is %llu bytes.\n", x.rlim_cur);
}
x.rlim_cur = try;
setrlimit(RLIMIT_STACK, &x);
// Test the limit with a do-nothing shell launch
int status = system("exit");
switch (status) {
case 0: return lower_maximum;
case -1:
perror("Failed to start shell");
return failed_to_launch;
default:
if (WIFEXITED(status) && WEXITSTATUS(status) == 127) {
// system() couldn't run /bin/sh, so assume it was
// because we set the stack limit too low.
return raise_minimum;
}
else if (WIFSIGNALED(status)) {
fprintf(stderr, "system() failed with signal %s.\n",
strsignal(WTERMSIG(status)));
return failed_to_launch;
}
else {
fprintf(stderr, "system() failed: %d.\n", status);
return failed_to_launch;
}
}
}
int main(void)
{
extern char **environ;
size_t etot = 0;
for (size_t i = 0; environ[i]; ++i) {
etot += strlen(environ[i]) + 1;
}
printf("Environment size = %lu\n", etot + sizeof(char*));
size_t tries = 0;
rlim_t try = 1 * 1000 * 1000, min = 0, max = 0;
while (1) {
enum try_code code = try_stack_limit(try);
switch (code) {
case lower_maximum:
// Call succeded, so lower max and try a lower limit.
++tries;
max = try;
printf("Lowered max to %llu bytes.\n", max);
try = min + ((max - min) / 2);
break;
case failed_to_launch:
if (tries == 0) {
// Our first try failed, so there may be a bug in
// the system() call. Stop immediately.
return 2;
}
// Else, consider it a failure of the new limit, and
// assume we need to limit it.
case raise_minimum:
// Call failed, so raise minimum and try a higher limit.
++tries;
min = try > min ? try : min;
rlim_t next = max ?
min + ((max - min) / 2) :
try * 2;
if (next == try) {
printf("Min stack size here for exec is %llu.\n", max);
return 0;
}
else {
printf("Raising limit from %llu to %llu.\n", try, next);
try = next;
}
break;
default:
return 1;
}
}
}
getrlimit
при запуске, сохранить значение, а затем вызватьsetrlimit
с этим значением перед вызовомexecl
? - person dbush   schedule 23.06.2017int arr[100]
), проверяя ограничения, не позволяйте VLA становиться слишком большими (также ограничивайтеalloca
) и ограничивайте рекурсию, вы не получите стека область кучи (что потребовало бы увеличения стека до терабайт в 64-битной системе, прежде чем они столкнутся). Большинство pgms построены для размера стека по умолчанию (например, Linux: 8 МБ), поэтому не нарушайте их. Перекодируйте свою программу, чтобы проверить все пределы, а не использоватьrlimit
. А как насчет других пгм? - person Craig Estey   schedule 23.06.2017NULL
return from (e.g.)malloc
,sbrk
). Вы хотите выполнить ручную проверку предельных значений, чтобы массив стека не заходил слишком далеко и не перезаписывал память стека выше (включая адрес возврата fnc) для выполнения произвольного кода в стеке при возврате fnc-- более распространенный вектор атаки. Или превышение одного массива кучи для перехода в другой [обычно прерывает pgms, но может быть создано как атака].rlimit
не смягчает их. P.S. Что такое Паскаль? :-) - person Craig Estey   schedule 23.06.2017RLIMIT_STACK
— это ограничение на размер самого стека. Сколько из этого пространства (в настоящее время) используется — это совершенно другой вопрос. Если начальный размер стека программы больше вашего предела, то она должна завершиться ошибкой при запуске, даже если изначально использовалось очень мало запрошенного пространства. - person John Bollinger   schedule 23.06.2017