Це гарна ідея викликати команди оболонки зсередини С?


50

Є команда оболонки unix ( udevadm info -q path -n /dev/ttyUSB2), яку я хочу викликати з програми C. Мабуть, близько тижня боротьби, я міг би знову реалізувати це, але я не хочу цього робити.

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


8
Одне, що слід враховувати, - це напади на ін’єкції.
MetaFight

8
Я в основному думав про випадок, коли ви подавали введення користувача як аргументи командної оболонки.
MetaFight

8
Чи може хтось помістити шкідливий udevadmвиконуваний файл у той самий каталог, що і ваша програма, і використовувати його для збільшення привілеїв? Я мало знаю про безпеку Unix, але чи буде працювати ваша програма setuid root?
Ендрю Пілізер

7
@AndrewPiliser Якщо поточний каталог не входить PATH, то ні - його доведеться запускати ./udevadm. Хоча IIRC Windows запускає виконувані файли з одним каталогом.
Ізката

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

Відповіді:


58

Це не особливо погано, але є деякі застереження.

  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, я погодився б. Однак ОП вказує Unix, коли розгортання є досить низькою вартістю, що важко сказати, чи динамічне завантаження бібліотеки гірше, ніж розгортання.
wallyk

1
Зазвичай я очікую, що бібліотека буде завантажена один раз , тоді як двійковий файл може бути виконаний кілька разів. Як завжди, це залежить від сценаріїв використання, але його потрібно враховувати (якщо згодом буде відхилено)
Брайан Агнев

3
Там немає «розгалуження» на Windows, так що цитата має бути Unix-специфічний.
Коді Грей

5
Ядро NT підтримує розгортання, хоча воно не відкривається через Win32. У будь-якому разі, я вважаю, що вартість запуску зовнішньої програми буде більше, execніж від fork. Завантаження розділів, зв'язок у спільних об'єктах, запуск коду init для кожного. А потім потрібно перетворити всі дані в текст, який потрібно розібрати з іншого боку, і для входів, і для виходів.
спектри

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
СПАСИБІ. Я так довго шукав лібудев. Я просто не міг його знайти!
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 та здійснює виклики equiv що стосується тих удевадм.

Це дозволило б уникнути безлічі недоліків, перелічених у чудовій відповіді Брайана Агнеу - наприклад, не покладаючись на існуючий бінарний шлях на певному шляху, уникаючи витрат на роздрібнення тощо.


Дякую, я знайшов це саме репо, і я в кінцевому підсумку переглянув його.
johnny_boy

1
… І libudev не особливо важкий у використанні. Її обробки помилок трохи бракує, але перерахування, запити та моніторинг API досить прості. Боротьба, за яку ваш страх може виявитися менше двох годин, якщо ви спробуйте.
спектри

7

Ваше запитання, здавалося, закликає до лісової відповіді, а відповіді тут схожі на дерева, тому я подумав, що я дам вам лісову відповідь.

Це дуже рідко, як написано програми C. Сценарії оболонки завжди написані, а іноді, як написані програми Python, perl або Ruby.

Зазвичай люди пишуть на C для зручного використання системних бібліотек та прямого доступу низького рівня до системних дзвінків ОС, а також для швидкості. І C - це складна мова для запису, тому якщо людям не потрібні ці речі, то вони не використовують C. Також, як правило, програми C мають залежність лише від спільних бібліотек та файлів конфігурації.

Обстріл до підпроцесу не особливо швидкий, і він не вимагає дрібнозернистого і контрольованого доступу до системних об'єктів низького рівня, і він вносить, можливо, дивовижну залежність від зовнішнього виконуваного файлу, тому це рідко можна побачити в програмах на С.

Є деякі додаткові проблеми. Безпека та портативність, про які згадують люди, є повністю дійсними. Вони однаково справедливі для скриптів оболонок, звичайно, але люди очікують таких проблем у скриптах оболонок. Але зазвичай не передбачається, що програми C мають такий клас безпеки, що робить його більш небезпечним.

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

І popenстворює процес дитини. Якщо ви що-небудь робите з обробкою сигналів або розгортанням процесів самостійно, ви можете отримати незвичайні SIGCHLDсигнали. Ваші дзвінки waitможуть дивно взаємодіяти popenі, можливо, створювати дивні умови для гонки.

Безпека та переносимість існують, звичайно,. Як і для скриптів оболонки або будь-якого іншого, що запускає інші виконувані файли в системі. І ви повинні бути обережні , що люди , які використовують програми не в змозі отримати оболонки мета-charcaters в рядок ви передаєте в popenтому , що рядок задається безпосередньо shз sh -c <string from popen as a single argument>.

Але я не думаю , що вони чомусь - то дивно бачити програму C допомогою popen. Причиною це дивно є те, що C зазвичай є мовою низького рівня, а popenне низьким рівнем. І тому, що використання popenобмежувачів дизайну місць у вашій програмі, тому що це буде дивно взаємодіяти зі стандартним входом та виходом вашої програми і доставить біль для власного управління процесами чи обробки сигналів. А тому, що програми C зазвичай не мають залежності від зовнішніх виконуваних файлів.


0

Ваша програма може зазнати злому тощо. Одним із способів убезпечити себе від цього виду діяльності є створення копії поточного машинного оточення та запуск програми за допомогою системи під назвою chroot.

З точки зору вашої програми її виконання виконується в звичайному оточенні, з точки зору безпеки, якщо хтось порушує вашу програму, вона має доступ лише до тих елементів, які ви надали під час створення копії.

Така установка називається кореневим острог для більш докладної інформації див CHROOT острогу .

Його зазвичай використовують для налаштування загальнодоступних серверів тощо.


3
ОП пише програму для запуску інших людей, не граючи з недовіреними бінарними файлами чи джерелом у власній системі.
Пітер Кордес
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.