Подтвердить что ты не робот

Как создать легковесную песочницу C кода?

Я хотел бы создать предварительный процессор/компилятор C, который позволяет собирать функции из локальных и онлайн-источников. то есть:

#fetch MP3FileBuilder http://scripts.com/MP3Builder.gz
#fetch IpodDeviceReader http://apple.com/modules/MP3Builder.gz

void mymodule_main() {
  MP3FileBuilder(&some_data);
}

Это легкая часть.

Жесткая часть Мне нужен надежный способ "песочницы" импортированного кода из прямого или неограниченного доступа к дисковым или системным ресурсам (включая выделение памяти и стек). Я хочу, чтобы безопасно запускать небольшие фрагменты ненадежного C-кода (модулей) без накладных расходов на их размещение в отдельном процессе, VM или интерпретаторе (отдельный поток был бы приемлемым, хотя).

ПОТРЕБНОСТИ

  • Мне нужно поставить квоты на доступ к данным и ресурсам, включая время процессора.
  • Я заблокирую прямой доступ к стандартным библиотекам
  • Я хочу остановить вредоносный код, который создает бесконечную рекурсию.
  • Я хочу ограничить статическое и динамическое распределение конкретными пределами
  • Я хочу поймать все исключения, которые модуль может поднять (например, делить на 0).
  • Модули могут взаимодействовать только с другими модулями через интерфейсы ядра.
  • Модули могут взаимодействовать только с системой (I/O и т.д.) через интерфейсы ядра.
  • Модули должны разрешать операции бит, математику, массивы, перечисления, петли и ветвление.
  • Модули не могут использовать ASM
  • Я хочу ограничить доступ указателей и массивов к памяти, зарезервированной для модуля (через пользовательский safe_malloc())
  • Должен поддерживать ANSI C или подмножество (см. ниже)
  • Система должна быть легкой и кросс-платформенной (включая встроенные системы).
  • Система должна быть совместима с GPL или LGPL.

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

не намерение использовать существующий код C без изменений для создания модуля. Цель состоит в том, что модули должны будут соответствовать набору правил и ограничений, предназначенным для ограничения модуля базовыми логическими и трансформационными операциями (например, например, для транскода или сжатия).

Теоретический вклад в такой компилятор/предварительный процессор будет представлять собой один файл ANSI C (или безопасное подмножество) с функцией module_main, NO включает в себя или предпроцессорные директивы, без ASM, он будет разрешать циклы, ветвление, функцию вызовы, математические указатели (ограниченные диапазоном, выделенным модулю), бит-сдвиг, битподы, приведения, перечисления, массивы, ints, float, strings и maths. Все остальное необязательно.

ПРИМЕР РЕАЛИЗАЦИИ

Вот фрагмент псевдокода, чтобы объяснить это лучше. Здесь модуль превышает его квоту выделения памяти, а также создает бесконечную рекурсию.

buffer* transcodeToAVI_main( &in_buffer ) {
    int buffer[1000000000]; // allocation exceeding quota
    while(true) {} // infinite loop
    return buffer;
}

Здесь представлена ​​преобразованная версия, в которой наш препроцессор добавил точки наблюдения для проверки использования и рекурсии памяти и завернул все это в обработчик исключений.

buffer* transcodeToAVI_main( &in_buffer ) {
    try {
        core_funcStart(__FILE__,__FUNC__); // tell core we're executing this function
        buffer = core_newArray(1000000000, __FILE__, __FUNC__); // memory allocation from quota
        while(true) {
           core_checkLoop(__FILE__, __FUNC__, __LINE__) && break; // break loop on recursion limit
        } 
        core_moduleEnd(__FILE__,__FUNC__);
    } catch {
        core_exceptionHandler(__FILE__, __FUNC__);
    }
    return buffer;
}

Я понимаю, что выполнение этих проверок влияет на производительность модуля, но я подозреваю, что он все же будет превосходить языки высокого уровня или виртуальные языки для задач, которые он предназначен для решения. Я не пытаюсь остановить модули, делающие опасные вещи прямо, я просто пытаюсь заставить эти опасные вещи проходить контролируемым образом (например, через обратную связь с пользователями). т.е.: "Модуль X превысил его распределение памяти, продолжить или прервать?".

UPDATE

Самое лучшее, что я получил до сих пор, - использовать пользовательский компилятор (как взломанный TCC) с проверкой границ, а также некоторые пользовательские функции и код цикла, чтобы поймать рекурсии. Мне все равно хотелось бы услышать мысли о том, что еще мне нужно проверить или какие решения есть там. Я полагаю, что удаление ASM и контрольных указателей перед использованием решает много проблем, выраженных в предыдущих ответах ниже. Я добавил щедрость, чтобы вырвать еще несколько отзывов из сообщества SO.

За щедрость, которую я ищу:

  • Подробная информация о потенциальных эксплойтах против теоретической системы, определенной выше
  • Возможная оптимизация по проверке указателей при каждом доступе
  • Экспериментальные реализации с открытым исходным кодом понятий (например, Google Native Client)
  • Решения, поддерживающие широкий диапазон ОС и устройств (без решений на базе ОС/аппаратных средств)
  • Решения, поддерживающие большинство операций C, или даже С++ (если это возможно)

Дополнительный кредит за метод, который может работать с GCC (т.е. предварительный процессор или небольшой патч GCC).

Я также рассмотрю всех, кто может окончательно доказать, что я пытаюсь сделать, вообще нельзя. Вы должны быть довольно убедительными, хотя ни одно из возражений до сих пор не прибило технических аспектов того, почему они считают это невозможным. В защиту тех, кто сказал, что этот вопрос не был первоначально поставлен как способ безопасного запуска С++. Я теперь уменьшил требование к ограниченному подмножеству C.

Мое понимание C можно классифицировать как "промежуточное", мое понимание аппаратного обеспечения ПК - это, возможно, шаг ниже "продвинутый". Постарайтесь направить свои ответы на этот уровень, если сможете. Поскольку я не эксперт по C, я буду во многом основываться на ответах, полученных на ответ, а также на то, насколько близко ответ отвечает моим требованиям. Вы можете помочь, предоставив достаточные доказательства для ваших требований (респондентов) и путем голосования (все остальные). Я назначу ответ, как только обратный отсчет щедрот достигнет 6 часов.

Наконец, я считаю, что решение этой проблемы станет важным шагом на пути к поддержанию актуальности C во все более сетевом и параноидальном мире. По мере того, как другие языки значительно сокращают разрыв, а вычислительная мощность возрастает, будет сложнее и сложнее обосновать добавленный риск развития C (как сейчас с ASM). Я считаю, что ваши ответы будут иметь гораздо большее значение, чем забивание нескольких точек SO, поэтому, пожалуйста, внесите свой вклад, даже если истечет срок действия.

4b9b3361

Ответ 1

Поскольку стандарт C слишком широк, чтобы его можно было разрешить, вам нужно пойти наоборот: укажите минимальное подмножество C, которое вам нужно, и попытайтесь его реализовать. Даже ANSI C уже слишком сложна и позволяет нежелательное поведение.

Аспект C, который является наиболее проблематичным, - это указатели: язык C требует указателя arithmitic, и те не проверяются. Например:

char a[100];
printf("%p %p\n", a[10], 10[a]);

оба будут печатать один и тот же адрес. Так как a[10] == 10[a] == *(10 + a) == *(a + 10).

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

Поскольку вы хотите, чтобы эта функция могла работать в одном и том же процессе (потенциально в другом потоке), вы обмениваетесь памятью между вашим приложением и "безопасным" модулем, поскольку все это имеет смысл иметь поток: обмениваться данными быстрее доступ. Однако это также означает, что оба потока могут читать и записывать одну и ту же память.

И поскольку вы не можете доказать время компиляции, когда указатели заканчиваются, вы должны сделать это во время выполнения. Это означает, что код типа "a [10]" должен быть переведен на нечто вроде "get_byte (a + 10)", в котором я больше не буду называть его C.

Родной клиент Google

Итак, если это правда, как это делает Google? Ну, в отличие от требований здесь (кросс-платформенная (включая встроенные системы)), Google концентрируется на x86, у которого есть дополнительные возможности для подкачки с защитой страниц, также сегментируют регистры. Это позволяет создать песочницу, в которой другой поток не использует одну и ту же память одинаково: песочница по сегментации ограничена изменением только собственного диапазона памяти. Кроме того:

  • собраны списки безопасных сборных конструкций x86
  • gcc изменен, чтобы испускать эти безопасные конструкции
  • этот список построен таким образом, который можно проверить.
  • после загрузки модуля эта проверка выполняется

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

Заключение

Итак, каков бы ни был ваш маршрут, вам нужно начать с чего-то нового, которое поддается проверке и только тогда вы можете начать с адаптации существующего компилятора или создания нового. Однако, пытаясь имитировать ANSI C, нужно подумать о проблеме с указателем. Google смоделировал свою изолированную программную среду не на ANSI C, а на подмножестве x86, что позволило им использовать существующие компиляторы в значительной степени с недостатком привязки к x86.

Ответ 2

Я думаю, что вы получите много информации о некоторых проблемах и вариантах реализации, которые Google сделал при разработке Native Client, системы для выполняя код x86 (надеемся, мы надеемся) в браузере. Возможно, вам понадобится выполнить некоторую переработку исходного кода или источник-источник, чтобы сделать код безопасным, если это не так, но вы должны иметь возможность полагаться на песочницу NaCL, чтобы поймать ваш сгенерированный код сборки, если он пытается сделать что-то слишком напуганное,

Ответ 3

Если бы я собирался это сделать, я бы исследовал один из двух подходов:

  • Используйте CERN CINT, чтобы выполнить изолированный код в интерпретаторе и посмотреть, как ограничить то, что разрешает интерпретатор. Это, вероятно, не дало бы ужасно хорошую производительность.
  • Используйте LLVM, чтобы создать промежуточное представление кода С++, а затем посмотреть, возможно ли выполнить этот байт-код в изолированной Java-среде -style VM.

Однако я согласен с другими, что это, вероятно, ужасно вовлеченный проект. Посмотрите на проблемы, которые имели веб-браузеры с багги или зависающими плагинами, дестабилизирующими весь браузер. Или посмотрите примечания к выпуску для проекта Wireshark; Кажется, почти каждый релиз содержит исправления безопасности для проблем в одном из своих дисконтов протокола, которые затем влияют на всю программу. Если бы песочница C/С++ была выполнима, я бы ожидал, что эти проекты будут привязаны к одному на данный момент.

Ответ 4

Я наткнулся на Tiny C Compiler (TCC). Это может быть то, что мне нужно:

*  SMALL! You can compile and execute C code everywhere, for example on rescue disks (about 100KB for x86 TCC executable, including C preprocessor, C compiler, assembler and linker).
* FAST! tcc generates x86 code. No byte code overhead. Compile, assemble and link several times faster than GCC.
* UNLIMITED! Any C dynamic library can be used directly. TCC is heading torward full ISOC99 compliance. TCC can of course compile itself.
* SAFE! tcc includes an optional memory and bound checker. Bound checked code can be mixed freely with standard code.
* Compile and execute C source directly. No linking or assembly necessary. Full C preprocessor and GNU-like assembler included.
* C script supported : just add '#!/usr/local/bin/tcc -run' at the first line of your C source, and execute it directly from the command line.
* With libtcc, you can use TCC as a backend for dynamic code generation.

Это очень маленькая программа, которая делает взлом на ней жизнеспособным вариантом (взломать GCC?, а не в этой жизни!). Я подозреваю, что это станет отличной базой для создания моего собственного ограниченного компилятора. Я удалю поддержку языковых функций, которые я не могу сделать безопасными, и обернуть или заменить распределение памяти и обработку цикла.

TCC уже может выполнять проверку границ доступа к памяти, что является одним из моих требований.

libtcc также является отличной возможностью, так как я могу самостоятельно управлять компиляцией кода.

Я не ожидаю, что это будет легко, но это дает мне надежду, что я могу получить производительность, близкую к C, с меньшими рисками.

По-прежнему хочется услышать другие идеи.

Ответ 5

Это не тривиально, но это не так сложно.

Вы можете запустить двоичный код в песочнице. Каждая операционная система делает это в течение всего дня.

Им придется использовать стандартную библиотеку (vs generic C lib). Ваша стандартная библиотека будет применять все элементы управления, которые вы хотите наложить.

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

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

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

Хотите ввести ограничения памяти? Поместите чек в malloc. Хотите ограничить количество стека? Ограничьте сегмент стека.

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

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

Ответ 6

Совершенно невозможно. Язык просто не работает таким образом. Концепция классов теряется очень рано в большинстве компиляторов, включая GCC. Даже если бы это было так, было бы невозможно связать каждое распределение памяти с живым объектом, не говоря уже о "модуле".

Ответ 7

Я не очень внимательно изучил это, но ребята, работающие над Chromium (ака Google Chrome), уже работают над песочницей, которая может быть интересной.

http://dev.chromium.org/developers/design-documents/sandbox/Sandbox-FAQ

Это открытый источник, поэтому его можно использовать.

Ответ 8

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

Конечно, этот вопрос спорный, если у вас есть код супервизора, работающий на более низком уровне звонка или являющийся интерпретируемым языком (т.е. эмуляция ресурсов компьютера).

Лучший способ сделать это - запустить код в другом процессе (ipc не так уж плохо), и использовать системные вызовы, такие как Ptrace в linuxes http://linux.die.net/man/2/ptrace

Ответ 9

Лиран указал в codepad.org в комментарии выше. Он не подходит, потому что он полагается на очень тяжелую среду (состоящую из ptrace, chroot и исходящего брандмауэра), однако я обнаружил там несколько g++-переключателей безопасности, которые, как я думал, я бы разделил здесь:

gcc 4.1.2 flags: -O -fmessage-length = 0 -fno-merge-константы -fstrict-aliasing -fstack-protector-all

g++ 4.1.2 flags: -O -std = С++ 98 -pedantic-errors -Wfatal-errors -Werror -Wall -Wextra -Wno-missing-field-initializers -Wwite-strings -Wno-deprecated -Wno-unused -Wno-non- virtual-dtor -Wno-variadic-macro -fmessage-length = 0 -ftemplate-depth-128 -fno-merge-константы -fno-nonansi-builtins -fno-gnu-keywords -fno-elide-constructors -fstrict-aliasing - fstack-protector-all -Winvalid-pch

Параметры описаны в Руководство GCC

То, что действительно привлекло мое внимание, было флагом стека-защиты. Я считаю, что это слияние этого исследовательского проекта IBM (Stack-Smashing Protector) с официальным GCC.

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

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

Ответ 10

8 лет спустя, и я обнаружил новую платформу, которая соответствует всем моим первоначальным требованиям. Веб-сборка позволяет безопасно запускать подмножество C/С++ внутри браузера и поставляется с аналогичными ограничениями безопасности для моих требований, таких как ограничение доступа к памяти и предотвращая небезопасные операции с ОС и родительским процессом. Он был реализован в Firefox 52 и есть многообещающие признаки, которые другие браузеры будут поддерживать в будущем.

Ответ 11

Хорошая идея, но я уверен, что то, что вы пытаетесь сделать, невозможно с C или С++. Если вы отбросили идею песочницы, это может сработать.

Java уже получила аналогичную (как в большой библиотеке стороннего кода) систему в Maven2

Ответ 12

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

Ответ 13

Кажется, вы пытаетесь решить две проблемы. В моем собственном коде у меня нет проблем с памятью памяти или проблем с рекурсией или бесконечными циклами.

Что вы, кажется, предлагаете, это другой, более ограниченный язык, чем С++. Это то, что вы можете продолжить, но, как отметили другие, вам придется написать для него компилятор - простая текстовая обработка не даст вам то, что вы хотите.