セキュリティ
LXCでX11アプリケーションのセキュリティを強化する
Enhancing X11 Application Security with LXC (dobrowolski.dev)
要約
この記事では、LXC(Linux Containers)を使用してWebブラウザやElectronベースのIMアプリケーションなどのX11アプリケーションにセキュリティ層を追加し、ホストシステムからの隔離を実現する方法を詳細に解説しています。ネットワーク設定からコンテナの作成、X11ソケットと音声設定まで、具体的な手順と設定例をArch Linuxをベースに紹介しており、他のディストリビューションにも応用可能です。これにより、アプリケーションの侵害があった場合でも、ホストシステムのセキュリティへの影響を最小限に抑えることができます。
全文翻訳
2025-12-05 LXCでX11アプリケーションのセキュリティを強化する
ウェブブラウザやElectronベースのIMアプリケーションにセキュリティのレイヤーを追加できると便利だと思いませんか?結局のところ、ブラウザが侵害された場合、ユーザーのホームディレクトリ全体が危険にさらされる可能性があります。LXCを使用してアプリケーションをホストシステムから隔離することで、これを軽減しましょう。この例で使用するシステムはArch Linuxですが、この手順は他のディストリビューションにも簡単に適応できるはずです。
ネットワーキング機能
まず、LXCをインストールし、事前設定する必要があります。以下のパッケージをインストールします。
# pacman -S lxc lxcfs
次に、LXCコンテナにネットワーキング機能を与える必要があります。そのためには、/etc/default/lxcファイルを編集し、以下の行を末尾に追加します。
USE_LXC_BRIDGE="true"
これで、LXCブリッジインターフェースを開始できます。対応するsystemdユニットを有効にして開始します。
# systemctl enable lxc-net.service --now
lxcbr0という新しいインターフェースが利用可能になっているはずです。以下で確認します。
# ip a show dev lxcbr0
コンテナの作成
前述のステップが完了したら、最初のアプリケーションコンテナを作成できます。まず、初期設定を作成しましょう。/etc/lxcに移動し、新しい設定ファイルを作成します。ウェブブラウザのコンテナを作成するので、www.confと名付けます。好みのエディタでファイルを開き、以下の行を追加します。
lxc.net.0.type = veth
lxc.net.0.link = lxcbr0
lxc.net.0.flags = up
lxc.net.0.hwaddr = 10:66:6a:xx:xx:xx
lxc.idmap = u 0 100000 65536
lxc.idmap = g 0 100000 65536
最初の4行は、コンテナが以前に作成したネットワークブリッジを使用することを指定します。次に、コンテナのUIDとGIDをホスト上のUIDとGIDにどのようにマップするかを定義します。目標は最大限のセキュリティであるため、非特権コンテナを使用します。これを実現するために、コンテナのIDをホストに存在しないIDの範囲にマップします。これにより、悪意のあるプロセスがコンテナから脱出した場合でも、ホストシステム上で意味のある権限を持たないことが保証されます。
idmapの理解
idmap設定がどのように機能するかを詳しく説明します。
lxc.idmap = [type] [container_id] [host_id] [range]
[type] – マッピングされるIDのタイプを指定します。オプション:
uはUID用
gはGID用
[container_id] – コンテナ内でマッピングする最初のUID/GID。この場合、0(コンテナのルート)です。
[host_id] – container_idがマップされるホスト上の開始UID/GID。ここでは、コンテナ内の0がホスト上の100000にマップされます。IDは順番に増加します。
container_id=1 → host_id=100001
container_id=1000 → host_id=101000
[range] – マッピングするUID/GIDブロックのサイズ。標準のLinux ID範囲全体を提供するために65536を使用します。
マッピングテーブルの例:
| container_id | host_id |
| ------------------ | ----------- |
| 0 | 100000 |
| 1 | 100001 |
| 1000 | 101000 |
| 65535 | 165535 |
次に、LXCに新しいコンテナで使用するUID/GIDマッピングを伝える必要があります。これを行うには、/etc/subuidと/etc/subgidの両方に次の行を追加します。
root:100000:65536
この行は、ホストユーザーのrootがLXCコンテナに対して100000〜165535の範囲でUIDマッピングを作成できることを意味します。これは、以前に作成した非特権コンテナ設定に直接対応しています。異なるマッピングを持つ別のコンテナを作成したい場合、たとえば、container_id=0をhost_id=200000にマッピングしたい場合は、subuid/subgidファイルに別の行を追加するだけです。
root:100000:65536
root:200000:65536
コンテナの起動
これで、準備した設定ファイルを使用してコンテナを作成する準備が整いました。この例では、debian:trixieイメージをベースとして使用します。
# lxc-create --config /etc/lxc/www.conf --name www -t download --- \
-d debian -r trixie -a amd64
コンテナが実行中であることを確認します。
# lxc-ls -f
セットアップが完了したら、コンテナにログインします。
# lxc-attach www /bin/bash
コンテナ内では、通常のLinuxシステムと同様に、必要なソフトウェアをインストールできます。たとえば、Firefoxを実行するには:
# apt update
# apt-get -y install firefox-esr
多くのX11アプリケーションはrootとしてうまく動作しないため、コンテナ内でアプリケーションを実行するための専用ユーザーを作成するのが最善です。
# /sbin/useradd -m -s /bin/bash www
X11のセットアップ
基本的なコンテナ設定は完了したので、X11アプリをホストするように設定してみましょう。まず、X11ソケットをマッピングする必要があります。現在Xサーバーを実行している場合、それは/tmp/.X11-unixの下にあります。また、コンテナがXサーバーに対して認証できるように.Xauthorityを与える必要があります。ホストのファイルを単純にマウントしても機能しません。各クッキーエントリはホスト名でキー付けされており、クライアントは実行されているマシンのホスト名と一致するエントリのみを使用しますが、コンテナ内では一致しません。解決策は、エントリのファミリーフィールドをFamilyWildワイルドカード (ffff) に置き換えて、任意のホストに一致するようにし、それをコンテナ用の新しい.Xauthorityにマージすることです。
: > /tmp/lxc.Xauthority && xauth nlist :0 | \
sed 's/^..../ffff/' | xauth -f /tmp/lxc.Xauthority nmerge -
# :0を$DISPLAYの値に置き換えてください
最後に、以下の環境変数を設定していることを確認する必要があります。
DISPLAY - ホストのDISPLAY値に設定します
XAUTHORITY - lxc.Xauthorityファイルがコンテナ内にマウントされるパスに設定します
これらの手順を実装するために、コンテナの設定ファイルを編集し、以下の行を追加しましょう。これは、lxc-createが/var/lib/lxc/www/configの下に生成したコンテナごとの設定であり、作成時に渡した/etc/lxc/www.confテンプレートとは異なることに注意してください。
lxc.environment = XAUTHORITY=/tmp/lxc.Xauthority
lxc.environment = DISPLAY=:0
lxc.mount.entry = /tmp/.X11-unix tmp/.X11-unix none bind,optional,create=dir,ro
lxc.mount.entry = /tmp/lxc.Xauthority tmp/lxc.Xauthority none bind,optional,create=file,ro
もう1つ、重要な詳細があります。非特権コンテナを構築したため、そのIDはidmapによってシフトされています(コンテナUID 0はホストUID 100000にマップされます)。しかし、.Xauthorityファイルはxauthによってモード0600で作成され、ホストユーザーが所有しています。このホストユーザーのUIDはマッピングされた範囲外にあります。そのため、コンテナ内ではファイルがnobodyによって所有されているように表示され、コンテナユーザーはそれを読み取ることができません。したがって、Xは「Authorization required」で接続を拒否します。最も簡単な解決策は、ホスト上でクッキーファイルを世界中で読み取り可能にすることです。
chmod 644 /tmp/lxc.Xauthority
0600のままにしたい場合は、代わりにコンテナユーザーのマッピングされたホストUIDに所有者を変更します(コンテナUID 1000のwwwユーザーの場合、ホストUIDは101000です)。
すべてがうまくいけば、以前にインストールしたブラウザを実行し、そのウィンドウがホストのデスクトップに表示されるはずです。
# lxc-start www
# lxc-attach www -- su www -c firefox
オプションで、コンテナ内でハードウェアアクセラレーションによるレンダリングとビデオデコードを行いたい場合は、/dev/driをマウントしてGPUをパススルーできます。
lxc.mount.entry = /dev/dri dev/dri none bind,optional,create=dir
サウンドのセットアップ
オーディオはディスプレイと同じパターンに従います。PipeWireはPulseAudio互換サーバーを出荷しているので、X11ソケットと同じように、ホスト上にPulseAudioソケットを公開し、それをコンテナにバインドマウントできます。まず、アプリケーションがソケットを使用できるように、コンテナ内にオーディオパッケージをインストールします。
# apt-get -y install pulseaudio pipewire-pulse
次に、~/.config/pipewire/pipewire-pulse.confのserver.addressに専用のPulseAudioソケットを追加するようにPipeWireに指示します。
pulse.properties = {
# ... keep the existing defaults ...
server.address = [ "unix:native" "unix:/tmp/pulse-socket-0" ]
}
ソケットが表示されるようにPulseAudioサーバーをリロードします。
systemctl --user restart pipewire-pulse
/tmp/pulse-socket-0が作成されているはずです。次に、それをコンテナにマウントし、PULSE_SERVERをそれに向けます。コンテナ設定に以下を追加します。
lxc.environment = PULSE_SERVER=unix:/tmp/pulse-socket-0
lxc.mount.entry = /tmp/pulse-socket-0 tmp/pulse-socket-0 none bind,optional,create=file,ro
クッキーファイルとは異なり、ソケットは世界中でアクセス可能であるため、以前の非特権コンテナの所有権の問題はここには適用されません。chmodする必要はありません。残りは