コンテナとセキュリティ:seccomp





潜在的に危険な、未検証の、または単に「生の」プログラムで作業するために、いわゆるサンドボックスがよく使用されます-厳密に制限された特別に割り当てられた環境。 サンドボックスで起動されたプログラムの場合、ネットワークへのアクセス、ホストマシン上のオペレーティングシステムとの対話機能、および入力/出力デバイスからの情報の読み取りは通常非常に制限されています。



最近、コンテナは信頼できないプログラムや安全でないプログラムを実行するためにますます使用されています。



しかし、コンテナは(多くの共通機能にもかかわらず)サンドボックスの完全な類似物ではありません-サンドボックスは通常、特定のアプリケーション用に「シャープ化」されており、コンテナ化はより普遍的な技術です。 そして、コンテナ内で実行されているアプリケーションは、カーネルにアクセスして侵害する可能性があります。 それが、現代のコンテナ化ツールがメカニズムを使用してセキュリティを向上させる理由です。 今日の記事では、そのようなメカニズムの1つであるseccompについて説明したいと思います。



まず、seccompがどのように機能するかを見てから、それがDockerでどのように使用されるかを示します。



Seccomp:最初の知り合い



Seccomp(セキュアコンピューティングの略)は、プロセスが使用するシステムコールを決定できるようにするLinuxカーネルエンジンです。 攻撃者が任意のコードを実行する機会を得た場合、seccompは以前に発表されなかったシステムコールの使用を許可しません。







SeccompはGoogleの開発です。 特に、プラグインを実行するためにGoogle Chromeブラウザーで使用されます。







prctl()システムコールは、seccompをアクティブにするために使用されます。







簡単なプログラムの例を使用して、これがどのように機能するかを見てみましょう。







#include <stdio.h> #include <unistd.h> #include <linux/seccomp.h> #include <sys/prctl.h> int main () { pid_t pid; printf("Step 1: no restrictions yet\n"); prctl (PR_SET_SECCOMP, SECCOMP_MODE_STRICT); printf ("Step 2: entering the strict mode. Only read(), write(), exit() and sigreturn() syscalls are allowed\n"); pid = getpid (); printf ("!!YOU SHOULD NOT SEE THIS!! My PID = %d", pid); return 0; }
      
      





このプログラムをseccomp1.cとして保存し、コンパイルして実行します。







 $ gcc seccomp1.c -o seccomp1 $ ./seccomp1
      
      





コンソールに次の出力が表示されます。







 Step 1: no restrictions yet Step 2: entering the strict mode. Only read(), write(), exit() and sigreturn() syscalls are allowed Killed
      
      





そのような結論がどこから来たのかを正確に理解するために、straceを使用します。







 $ strace ./seccomp1 /   / prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT) = 0 write(1, "Step 2: entering the strict mode"..., 100Step 2: entering the strict mode. Only read(), write(), exit() and sigreturn() syscalls are allowed ) = 100 +++ killed by SIGKILL +++ Killed
      
      





それで何が起こったのですか? prctlシステムコールを使用して、seccompをアクティブにし、strictモードをオンにしました。 その後、プログラムはgetpid()システムコールを使用して現在のプロセスのPIDを見つけようとしましたが、課された制限によりこれが許可されませんでした。プロセスはSIGKILLシグナルを受信し、すぐに終了しました。







ご覧のとおり、seccompの仕事はうまくいきます。 ただし、厳密モードは、解決するシステムコールと解決しないシステムコールを選択できないという点で不便です。 この問題を解決するために、 BPF(Berkeley Packet Filters)メカニズムを使用できます。







SeccompおよびBPFフィルター



BPF(Berkeley Packet Filtersの略)は元々、ネットワークパケットをフィルタリングするために作成されましたが、その後その範囲は大幅に拡大しました。 現在、BPFは、たとえばLinuxカーネルのトレースに使用されています(Brendan Greggのブログにこのトピックに関する興味深い出版物があります)。 2012年には、seccompと統合されました。 seccomp-bpfと呼ばれる拡張バージョンが登場しました。







BPFの作成は非常に複雑な作業です(たとえば、 ここで何かを読むことができます )。 BPF構文の機能については説明せず(このトピックは記事の範囲をはるかに超えています)、libseccompライブラリを使用します。これは、システムコールをフィルタリングするためのシンプルで便利なAPIを提供します。



標準のパッケージマネージャーを使用してインストールされます。







 $ sudo apt-get install libseccomp-dev
      
      





では、小さなプログラムを作成してみましょう。







 #include <stdio.h> #include <seccomp.h> #include <unistd.h> int main() { pid_t pid; scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(sigreturn), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0); printf ("No restrictions yet\n"); seccomp_load(ctx); pid = getpid(); printf("!! YOU SHOULD NOT SEE THIS!! My PID is%d\n", pid); return 0; }
      
      





このコードを1行ずつコメントしてみましょう。







 scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL);
      
      





ここで、フィルターを初期化し、デフォルトで実行するアクションを示します。この場合、これはSCMP_ACT_KILLです。つまり、禁止されたシステムコールを実行するプロセスを即座に停止します。







次に、seccompのルールがあります。 その中で、プロセスの実行を許可するシステムコールを示します。







  seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(sigreturn), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
      
      





次に、ルールをアクティブにします。







 seccomp_load(ctx);
      
      





前の例のように、現在のプロセスのPIDをコンソールに表示しようとしています。 しかし、それはできますか?







プログラムをコンパイルして実行します。



 $ gcc -o seccomp2 seccomp2.c -lseccomp $ ./seccomp2
      
      





次の出力が表示されます。



 No restrictions yet Bad system call
      
      





このプログラムの実行中に何が起こりましたか? 前のケースと同様に、straceはこの質問に答えるのに役立ちます。







 $ strace ./seccomp2 /  / prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, {len = 9, filter = 0x1ef5fe0}) = 0 +++ killed by SIGSYS +++
      
      





フィルターが機能していることがわかります。プロセスはgetpidシステムコールを行いましたが、これはルールで禁止されており、すぐに停止しました。







seccompフィルターがどのように機能するかをよりよく理解するには、SCMP_ACT_KILLではなくSCMP_ACT_TRAPをコードのデフォルトアクションとして指定すると便利です。







 scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_TRAP);
      
      





straceの出力はより詳細になります。



 $ strace ./seccomp2 /  / syscall_18446744073709551615(0xffffffff, 0x7feb8c47ab28, 0, 0x22b, 0x130c0c0, 0) = 0x27 --- SIGSYS {si_signo=SIGSYS, si_code=SYS_SECCOMP, si_call_addr=0x7feb8c18366f, si_syscall=__NR_getpid, si_arch=AUDIT_ARCH_X86_64} --- +++ killed by SIGSYS +++
      
      





私たちのケース(OS Ubuntu 16.04、カーネル4.4)では、出力は禁止されたシステムコール、プロセスの停止を伴う実行試行、si_syscall = __ NR_getpidを直接示します。







他のディストリビューションおよび他のバージョンのカーネルでは、出力にシ​​ステムコールの名前が含まれず、ファイル/asm/unistd.hからの番号が含まれる場合があります。









DockerのSeccomp



前のセクションでは、seccompの基本原則について説明しました。 次に、特定のコンテナー化ツールでseccompを使用する方法のDockerの例を見てみましょう。



コンテナのseccompプロファイルがruncに初めて登場しました。これについては既に説明しました







Docker Engineでは、バージョン1.10以降に追加されています。







デフォルトでは、44個のシステムコールがすべてのDockerコンテナでブロックされています(最新の64ビットLinuxシステムには数百のシステムコールがあります)。 たとえば、reboot()システムコールは禁止されています。コンテナからホストマシンのOSを再起動する必要がある状況はほとんど想像できません。







もう1つの良い例は、keyctl()システムコールで、最近脆弱性が発見されました( CVE 2016-0728 )。 Dockerでは、デフォルトでブロックされています。







デフォルトのseccompプロファイルは優れた技術革新であり、攻撃者の可能性を制限し、攻撃の可能性を減らすという点で既に有用です。 しかし、これは明らかに十分ではありません。ロック解除された呼び出しの多くには脆弱性があります。 明らかな理由により、潜在的に危険な呼び出しをすべて禁止することは、単に不可能です!







これが、コンテナがシステムコールをフィルタリングする機能を提供する理由です。 すべてのフィルターは、JSON形式の構成ファイルに登録されます。







以下に簡単な例を示します。







 { "defaultAction":"SCMP_ACT_KILL", "syscalls":[ { "name":"chmod", "action":"SCMP_ACT_ERRNO" } ] }
      
      





ご覧のとおり、ここではすべてが上記のコード例とまったく同じように実行されます。 最初に、デフォルトで実行するアクションを指定します。 次に、禁止された呼び出しと、これらの呼び出しのいずれかを実行するときに実行する必要があるアクションをリストします。







このファイルをconfig.jsonとして保存し、上記のseccomp設定でコンテナーを起動してみてください。







 $ docker run --security-opt seccomp:chmod.json busybox chmod 400 /etc/hostname chmod: /etc/hostname: Operation not permitted
      
      





ご覧のとおり、フィルターは定式化されたルールに従って機能しました。禁止されたシステムコールchmodはブロックされました。







おわりに



この記事では、seccompがどのように機能し、Dockerでどのように使用されるかについて説明しました。 質問、コメント、提案がある場合-コメントへようこそ。







結論として、私たちはいつものように、さらに学びたい人のために役立つリンクを提供します:










All Articles