👶

Capabilityを実感する

2023/02/25に公開

概要

コンテナ技術入門でpingを使ったcapabilityの説明があったが, 自分が試した現状最新(2023/02/25におけるEC2無料枠において)のUbuntu 20.04 LTSではpingはSUIDが設定されておらずcapabilityの説明で行われていたことができなかったため自分で試してみた.

準備

まずは以下のようなsocketを80番ポートにbindして終了するだけのプログラムを用意する.

bind_socket.c
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
 
int main() {
    int sockfd;
    struct sockaddr_in addr;
 
    if( ( sockfd = socket( AF_INET, SOCK_STREAM, 0 ) ) < 0 ) {
        perror( "socket" );
        exit(1);
    }
 
    addr.sin_family = AF_INET;
    addr.sin_port = htons( 80 );
    addr.sin_addr.s_addr = INADDR_ANY;
 
    if( bind( sockfd, (struct sockaddr *)&addr, sizeof( addr ) ) < 0 ) {
        perror( "bind" );
        exit(1);
    }

    return 0;
}

実験

socketを1024未満の特権ポートにbindするにはroot権限が必要なので, permission deniedエラーが出る.

gcc bind_socket.cpp -o bind_socket
./bind_socket 
bind: Permission denied

echo $?
1

これをroot所有でSUID指定をすると非特権ユーザでも実行することができるようになる.

sudo chown root:root bind_socket
sudo chmod 4711 bind_socket
./bind_socket
echo $?
0

このようにSUID指定により非特権ユーザでbind_socketを実行してもroot権限でアプリケーションが動いたことにより特権ポートにsocketをbindすることができた.
しかしながらこれはsocketを特権ポートにbindするためだけににrootによる全権限でアプリケーションを動かしていることになり, もしこのbind_socket.cppに脆弱性があった場合rootの全権限を悪用される可能性があるためSUIDでrootを指定するのはセキュリティ的によろしくない.

そこでcapabilityの登場になる. capabilityによりrootの持つ権限を現状全37種類に分割して必要最小限の権限だけをアプリケーションに付与して実行することができる. 以下はcapabilityのドキュメントおよび定義.

実際にcapabilityの種類のうち特権ポートにsocketをbindする権限であるCAP_NET_BIND_SERVICEを与えてアプリケーションを実行してみる(ubuntuはEC2のubuntuイメージを使った際のデフォルトのユーザ).

sudo chown ubuntu:ubuntu bind_socket
chmod 711 bind_socket
sudo setcap 'cap_net_bind_service=+ep' bind_socket 
./bind_socket
echo $?
0

CAP_NET_BIND_SERVICEを付与することにより, 非特権ユーザでも特権ポートにsocketをbindするアプリケーションを実行できるようになった.

所感

ひとまずcapabilityの概念を抑えるための触りだけをまとめた.
少なくともubuntuの20系ではpingにrootでSUIDが指定されていなかったためこのようなまとめを作成したが, 実際にプログラムを書いてみるとどこで落ちるかまで分かるためより直感的な理解につながるのではないかと感じた. pingが修正されていて結果的に良かったと思う.

参照

コンテナ技術入門
Linuxカーネルのケーパビリティ[1]

Discussion