C内からシェルコマンドを呼び出すことをお勧めしますか?


50

udevadm info -q path -n /dev/ttyUSB2Cプログラムから呼び出したいUNIXシェルコマンド()があります。おそらく約1週間の闘争で、私はそれを自分で再実装することができましたが、私はそれをしたくありません。

単に電話するのは広く受け入れられている良い習慣popen("my_command", "r");ですか?それは受け入れられないセキュリティ問題を引き起こし、互換性の問題を転送しますか?このようなことをするのは間違っているように感じますが、なぜそれが悪いのかを指で確かめることはできません。


8
考慮すべき1つのことは、インジェクション攻撃です。
メタファイト

8
私は主に、シェルコマンド引数としてユーザー入力を提供した場合を考えていました。
メタファイト

8
誰かがudevadmあなたのプログラムと同じディレクトリに悪意のある実行可能ファイルを置き、それを使用して特権を昇格させることはできますか?UNIXのセキュリティについてはあまり知りませんが、あなたのプログラムはで実行されsetuid rootますか?
アンドリューピラザー

7
@AndrewPiliser現在のディレクトリがにない場合、PATHいいえ-で実行する必要があります./udevadm。ただし、IIRC Windowsは同じディレクトリの実行可能ファイルを実行します。
イズカタ

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を指定しているため、ライブラリの動的なロードがフォークよりも悪いかどうかを判断するのは困難です。
ウォリック

1
通常、ライブラリが1回ロードされると予想されますが、バイナリは複数回実行される場合があります。相変わらず、使用シナリオに依存しますが、考慮する必要があります(その後却下される場合)
ブライアンアグニュー

3
Windowsには「分岐」がないため、引用 Unix固有でなければなりません。
コーディグレー

5
NTカーネルはフォークをサポートしていますが、Win32を介して公開されていません。とにかく、外部プログラムを実行するコストは、からexecよりも多くなると思いforkます。セクションの読み込み、共有オブジェクトのリンク、それぞれの初期化コードの実行。そして、入力と出力の両方について、すべてのデータをテキストに変換して、反対側で解析する必要があります。
スペクトル

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
予防原則が本当に「あなたが知っている最も不注意な開発者が安全でない変更を行っていると仮定する」であり、悪用につながらないことを保証するために今すぐコードを書かなければならなかった場合、私はベッドから出ることができませんでした。私が知っている最も不注意な開発者は、Machievelliを彼が試みていないように見せます。
スティーブジェソップ

16

あなたが呼び出す特定のケースでは、ライブラリとしてudevadmプルインしudev、代替として適切な関数呼び出しを行うことができると思いますか?

例えば、あなたは、それ自体が「情報」モードにするとき呼び出しを行っているudevadm何を見てみることができます: https://github.com/gentoo/eudev/blob/master/src/udev/udevadm-info.cと当量の呼び出しを行いますそれらについてはudevadmが作成しています。

これにより、ブライアンアグニューの優れた答えに列挙されている多くのマイナス面のトレードオフを回避できます。たとえば、特定のパスに存在するバイナリに依存しない、分岐の費用を回避するなどです。


おかげで、私はその正確なレポを見つけました、そして、私はそれを少しの間調べました。
johnny_boy

1
…そしてlibudevの使用は特に難しくありません。エラー処理には少し欠けていますが、列挙、クエリ、監視APIは非常に簡単です。試してみると、あなたの恐怖の闘いは2時間未満になるかもしれません。
スペクトル

7

あなたの質問は森の答えを求めているようで、ここでの答えは木の答えのように見えるので、私はあなたに森の答えをあげると思いました。

これは、Cプログラムがどのように記述されるかということは非常にまれです。それは常にシェルスクリプトの記述方法であり、時にはPython、perlまたはRubyプログラムの記述方法でもあります。

人々は通常、システムライブラリを簡単に使用し、OSシステムコールに低レベルで直接アクセスするため、および速度のためにCで記述します。また、Cは書くのが難しい言語なので、人々がそれらを必要としない場合、Cを使用しません。また、Cプログラムは通常、共有ライブラリと構成ファイルにのみ依存することが期待されます。

サブプロセスへのシェルアウトは特に高速ではなく、低レベルのシステム機能へのきめ細かく制御されたアクセスを必要とせず、外部実行可能ファイルへの驚くべき依存性を導入するため、参照することはまれですCプログラム。

いくつかの追加の懸念事項があります。人々が言及したセキュリティと移植性の懸念は完全に有効です。もちろん、シェルスクリプトにも同様に有効ですが、人々はシェルスクリプトにこの種の問題を期待しています。しかし、Cプログラムは通常、この種のセキュリティ上の懸念があるとは考えられていないため、より危険になります。

しかし、私の意見では、方法に関する最大の懸念はpopen、プログラムの他の部分と相互作用することです。popen子プロセスを作成し、その出力を読み取り、終了ステータスを収集する必要があります。その間、そのプロセスのstderrはプログラムと同じstderrに接続され、混乱を招く可能性があり、そのstdinはプログラムと同じであり、他の興味深い問題を引き起こす可能性があります。これは、シェルによって解釈される</dev/null 2>/dev/nullためpopen、渡す文字列に含めることで解決できます。

そしてpopen、子プロセスを作成します。シグナル処理または分岐プロセスで自分で何かを行うと、奇妙なSIGCHLDシグナルを受け取ることになります。への呼び出しは、奇妙な競合状態とwait奇妙に相互作用しpopen、場合によっては作成する可能性があります。

もちろん、セキュリティと移植性の問題があります。シェルスクリプトなど、システム上の他の実行可能ファイルを起動するためのものです。そして、あなたはあなたのプログラムを使用して、人々はあなたに渡す文字列にシェルのメタcharcatersを取得することができないことに注意する必要がありpopen、その文字列が直接与えられているので、shsh -c <string from popen as a single argument>

しかし、私はそれらがCプログラムを使用して見ているのは奇妙だとは思わないpopen。奇妙な理由は、Cは通常低レベルの言語であり、低レベルでpopenはないためです。popen場所の使用はプログラムの制約を設計するため、プログラムの標準入出力と奇妙に相互作用し、独自のプロセス管理や信号処理を行うのが面倒になるからです。また、Cプログラムは通常、外部実行可能ファイルに依存することを期待されていません。


0

プログラムはハッキングなどの影響を受ける可能性があります。この種のアクティビティから身を守るための1つの方法は、現在のマシン環境のコピーを作成し、chrootと呼ばれるシステムを使用してプログラムを実行することです。

プログラムの通常の環境での実行の観点から、セキュリティの観点から、誰かがあなたのプログラムを壊した場合、コピーを作成したときに提供した要素にしかアクセスできません。

詳細は、参照のためにこのような設定はchroot監獄と呼ばれている のchroot監獄を

通常、公的にアクセス可能なサーバーなどのセットアップに使用されます。


3
OPは、他の人が実行するためのプログラムを作成していますが、信頼できないバイナリやソースを自分のシステムで遊んでいるわけではありません。
ピーターコーデス
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.