Это хорошая идея для вызова команд оболочки из C?

50

Есть команда оболочки unix ( udevadm info -q path -n /dev/ttyUSB2), которую я хочу вызвать из программы на Си. Возможно, с неделей борьбы, я мог бы реализовать ее сам, но я не хочу этого делать.

Является ли для меня общепринятой практикой просто звонить popen("my_command", "r");или это вызовет неприемлемые проблемы с безопасностью и перенаправит проблемы совместимости? Мне кажется неправильным делать что-то подобное, но я не могу понять, почему это будет плохо.

Джонни Мальчик
источник
8
Одна вещь, чтобы рассмотреть, инъекционные атаки.
MetaFight
8
Я в основном думал о случае, когда вы вводили пользовательский ввод в качестве аргументов команды оболочки.
MetaFight
8
Может ли кто-нибудь поместить вредоносный udevadmисполняемый файл в тот же каталог, что и ваша программа, и использовать его для повышения привилегий? Я не знаю много о безопасности Unix, но будет ли запускаться ваша программа setuid root?
Эндрю говорит восстановить Монику
7
@AndrewPiliser Если текущего каталога нет PATH, то нет - его нужно запускать ./udevadm. Windows IIRC будет запускать исполняемые файлы в одном каталоге.
Изката
4
По возможности вызывайте нужную программу напрямую, не проходя через оболочку.
CodesInChaos

Ответы:

59

Это не особенно плохо, но есть некоторые оговорки.

  1. насколько портативным будет ваше решение? Будет ли выбранный вами двоичный файл работать одинаково везде, выводить результаты в том же формате и т. Д.? Будет ли он выводиться по-разному в настройках LANGи т. Д.?
  2. сколько дополнительной нагрузки это добавляет к вашему процессу? Формирование двоичного кода приводит к гораздо большей нагрузке и требует больше ресурсов, чем выполнение библиотечных вызовов (вообще говоря). Это приемлемо в вашем сценарии?
  3. Есть ли проблемы с безопасностью? Может ли кто-то заменить выбранный вами двоичный файл другим и после этого совершать гнусные поступки? Используете ли вы предоставленные пользователем аргументы для своего двоичного файла и могут ли они предоставить ;rm -rf /(например) (обратите внимание, что некоторые API-интерфейсы позволят вам указать аргументы более безопасно, чем просто предоставить их в командной строке)

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

Как вы заметили, другая проблема заключается в том, сколько работы нужно, чтобы воспроизвести то, что делает двоичный файл? Использует ли он библиотеку, которую вы также можете использовать?

Брайан Агнью
источник
13
Forking a binary results in a lot more load and requires more resources than executing library calls (generally speaking). Если бы это было на Windows, я бы согласился. Тем не менее, OP определяет Unix, где разветвление является достаточно низкой стоимостью, поэтому трудно сказать, хуже ли динамическая загрузка библиотеки, чем разветвление.
Wallyk
1
Обычно я ожидаю, что библиотека будет загружена один раз , тогда как двоичный файл может выполняться несколько раз. Как всегда, это зависит от сценариев использования, но должно быть рассмотрено (если впоследствии будет отклонено)
Брайан Агнью,
3
Там нет «разветвления» на Windows, так что цитата имеет быть Unix-специфично.
Коди Грей
5
Ядро NT поддерживает разветвление, хотя оно не доступно через Win32. Во всяком случае, я считаю, что стоимость запуска внешней программы будет больше, execчем от fork. Загрузка разделов, ссылки в общих объектах, запуск кода инициализации для каждого. А затем необходимо преобразовать все данные в текст для анализа на другой стороне, как для входных, так и для выходных данных.
spectras
8
Честно говоря, udevadm info -q path -n /dev/ttyUSB2в любом случае не будет работать на Windows, так что все разветвленные дискуссии немного теоретичны.
MSalters
37

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

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

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

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

#include <libudev.h>
#include <sys/stat.h>
#include <stdio.h>

int main(int argc, char* argv[]) {
    struct stat statbuf;

    if (stat("dev/ttyUSB2", &statbuf) < 0)
        return -1;
    struct udev* udev = udev_new();
    struct udev_device *dev = udev_device_new_from_devnum(udev, 'c', statbuf.st_rdev);

    printf("%s\n", udev_device_get_devpath(dev));

    udev_device_unref(dev);
    udev_unref(udev);
    return 0;
}

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

Карл Билефельдт
источник
2
БЛАГОДАРЮ ВАС. Я так долго искал libudev. Я просто не мог найти это!
johnny_boy
2
Я не вижу, как здесь возникают проблемы с «инъекционными уязвимостями», если только программа OP не вызывается, когда другой пользователь имеет контроль над своей средой, что в первую очередь имеет место в случае suid (также возможно, но в меньшей степени, такие вещи, как принудительная команда cgi и ssh). Нет причин, по которым обычные программы должны быть защищены от пользователя, от которого они работают.
R ..
2
@R ..: «Уязвимость внедрения» - это когда вы обобщаете ttyUSB2и используете sprintf(buf, "udevadm info -q path -n /dev/%s", argv[1]). Теперь это выглядит довольно безопасно, но что, если argv[1]есть "nul || echo OOPS"?
MSalters
3
@R ..: вам просто нужно больше воображения. Сначала это фиксированная строка, затем это аргумент, предоставленный пользователем, затем это аргумент, предоставленный какой-то другой программой, запускаемой пользователем, но которую он не понимает, затем это аргумент, который их браузер извлек из веб-страницы. Если дела идут «вниз», то в конечном итоге кто-то должен взять на себя ответственность за дезинфекцию входных данных, и Карл говорит, что ему необходимо проявить крайнюю осторожность, чтобы определить эту точку, где бы она ни возникала.
Стив Джессоп
6
Хотя, если принцип предосторожности действительно был «предположим, что самый небрежный разработчик, которого вы знаете, вносит небезопасные изменения», и мне пришлось сейчас написать код, чтобы гарантировать, что это не приведет к эксплойту, то лично я не смог бы встать с постели. Самые небрежные разработчики, которых я знаю, заставляют Макиевелли выглядеть так, будто он даже не пытается .
Стив Джессоп
16

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

Например, вы можете посмотреть, что делает udevadm при вызове в режиме «информация»: https://github.com/gentoo/eudev/blob/master/src/udev/udevadm-info.c и сделать эквивалентные вызовы. что касается тех, кто делает udevadm.

Это позволило бы избежать многих недостатков, перечисленных в отличном ответе Брайана Агнью - например, не полагаться на двоичный файл, существующий на определенном пути, избегая затрат на разветвление и т. Д.

Адам Кроускоп
источник
Спасибо, я нашел этот точный репо, и я немного его просмотрел.
johnny_boy
1
… И libudev не особенно сложен в использовании. Его обработки ошибок немного не хватает, но API-интерфейсы перечисления, запросов и мониторинга довольно просты. Борьба, в которой ваш страх может оказаться менее 2 часов, если вы попробуете.
spectras
7

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

Это очень редко, как пишутся программы на Си. Это всегда, как пишутся сценарии оболочки, а иногда и как программы на Python, Perl или Ruby.

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

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

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

Но, на мой взгляд, самые большие проблемы связаны с тем, как popenбудет взаимодействовать с остальной частью вашей программы. popenдолжен создать дочерний процесс, прочитать его выходные данные и получить статус выхода. Между тем, stderr этого процесса будет подключен к тому же stderr, что и ваша программа, что может привести к сбивающему с толку выводу, а его stdin будет таким же, как ваша программа, что может вызвать другие интересные проблемы. Вы можете решить эту проблему, включив </dev/null 2>/dev/nullв popenнее передаваемую строку, поскольку она интерпретируется оболочкой.

И popenсоздает дочерний процесс. Если вы что-то делаете с процессами обработки сигналов или разветвления самостоятельно, вы можете получить странные SIGCHLDсигналы. Ваши призывы waitмогут странным образом взаимодействовать popenи, возможно, создавать странные условия гонки.

Проблемы безопасности и переносимости, конечно, есть. Как и для сценариев оболочки или всего, что запускает другие исполняемые файлы в системе. И вы должны быть осторожны, чтобы люди, использующие вашу программу, не могли получить метасимволы оболочки в строку, в которую вы передаете, popenпотому что эта строка передается непосредственно с shпомощью sh -c <string from popen as a single argument>.

Но я не думаю, что по этой причине странно видеть использование программы на Си popen. Причина в том, что это странно, потому что C, как правило, язык низкого уровня, а popenне низкого уровня. И потому что использование popenнакладывает конструктивные ограничения на вашу программу, потому что она будет странным образом взаимодействовать со стандартным вводом и выводом вашей программы и затруднит управление вашим собственным процессом или обработкой сигналов. А поскольку программы на Си, как правило, не должны зависеть от внешних исполняемых файлов.

всевозможный
источник
0

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

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

Такая настройка называется chroot jail, более подробно см. Chroot jail .

Обычно он используется для настройки общедоступных серверов и т. Д.

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