Mar
23rd
Tue
23rd
lxcについてくるlxc-execute(1)がお手軽すぎてヤバい
lxcは普段は仮想マシンみたいなのを作る用途で使うわけだけども、そんな大規模の必要ないよプロセスが一個ぽつんと隔離されて起動してくれればいいよ、というありがちなケースをカバーしてくれるのがlxc-execute(1)で、つまりこれはsudo(1)とかchroot(1)とかfreebsdのjail(1)みたいな使い方をするわけだ。特徴としては、
- まずpid/uidの名前空間が他と分離されるので、仮にrootが奪取されても他のプロセスをkillしたりできない
- ネットワークも他と分離されるので、仮にrootが奪取されてもパケットスニファとかからは何も見えない
- もちろんファイルシステムも分離されてるので、仮にroot奪取されてもファイル弄られたりしない
- デバイスへのアクセスも禁止なので、仮にroot奪取されても/dev/sda1をmknodして適当なマウントポイントにマウントとか不可、/dev/kmemも見えない
- で、さらに、設定さえ書けばそれらをあえて他と共有させる事も可能
うぬ完璧だ。まあKernelの穴を突かれるとNGってのはあるので、油断は禁物けどな。あとなあ、デフォルトがちょっといけてないなあ。lxc(7)のmanに
lxc-execute -n foo -f lxc-macvlan.conf /bin/bash
って書いてあるけど、これだとほとんど全部のリソースがbashから見えてる。いけてない。デフォルトは安全な方に倒すべきだろ。無引数で起動すると何もかもが見えない状態からスタートすべき。そんでlibc.soがリンクできなくて起動失敗する所から始まるべき。設定緩めるのは設定書けばいいんだから。
まあ、ないもんはしゃあないから自分で作ってみようか。プロセスが起動するのに最低限何が必要かを考えると、
- そのバイナリ
- そいつがリンクしてるライブラリ – ldd(1)すりゃ分かる
- 特権放棄のためsudo
- sudoがリンクしてるライブラリ
- sudoが要求する/etc/sudoersと/etc/passwd
- libcが要求する/lib/libnss_files.soとか系
- lxc-executeが要求する/usr/lib/lxc/lxc-init
- lxc-initが要求する/procと/dev/shm (マウントポイントが必要)
こんなもんかな。とあるバイナリを受けたら、いろんなものを殺した設定と上記ファイル群を適当なディレクトリにreadonly bind mountで展開してから lxc-execute -- sudo -u bar foo
とかを実行するスクリプトがありゃいいわけだよ。そんなに難しくないべ(下記)? lxc-sandbox.rbとか名前つけて保存すると、
zsh % sudo ruby lxc-sandmox.rb /bin/bash
sudo: unable to resolve host lxc-testhead
sudo: unable to initialize PAM: No such file or directory
bash-4.1$ ls /
bash: ls: command not found
bash-4.1$ ^D
zsh % sudo ruby lxc-sandmox.rb /bin/mknod /dev/kmem c 1 2
sudo: unable to resolve host lxc-testhead
sudo: unable to initialize PAM: No such file or directory
/bin/mknod: `/dev/kmem': Operation not permitted
なんも見えない感じだね。よさげに動いている模様。resolv.confがないとかpamがないとか怒られてるけど、まあそのへんは適当に修正してもらってもいいし、なくても動くからこのままでもいいんじゃねという気はするね。
#! /path/to/ruby
require "pathname"
require "tmpdir"
require "fileutils"
def mount_em_all bin
files = [bin]
ret = []
`ldd #{bin}`.each_line do |i|
m = %r"/\S+".match i
if m
j = Pathname.new m.to_s
files.push j
end
end
while i = files.pop do
if i.lstat.ftype == "link"
j = i.readlink
j = i.dirname + j if j.relative?
files.push j
end
ret.push i
end
return ret
end
passwd = <<END
root:*:0:0:root:/root:/bin/sh
nobody:*:65534:65534:nobody:/nonexistent:/bin/sh
END
bin = Pathname.new ARGV.first
sudo = Pathname.new `/bin/which sudo`.chomp
lxcinit = Pathname.new "/usr/lib/lxc/lxc-init"
root = Pathname.new "/"
Dir.mktmpdir bin.basename.to_s do |d|
dir = Pathname.new d
rootfs = dir + "rootfs"
fstab = dir + "fstab"
lxcconf = dir + "lxc.conf"
entries = []
rootfs.mkpath
Dir.chdir rootfs do
files = mount_em_all bin
files += mount_em_all sudo
files += mount_em_all lxcinit
Pathname.glob "/lib/libnss_*" do |e|
files += mount_em_all e
end
files.each do |i|
r = i.relative_path_from root
r.dirname.mkpath
FileUtils.touch r
entries.push r.to_s
end
FileUtils.mkdir_p "proc"
FileUtils.mkdir_p "dev/shm"
FileUtils.mkdir_p "etc"
Dir.chdir "etc" do
open "passwd", "w" do |f| f.puts passwd end
open "sudoers", "w" do |f|
# only to throw away root privilege
f.puts "root ALL=(nobody) #{bin}"
end
File.chmod 0440, "sudoers"
File.chmod 0644, "passwd"
end
File.chmod 0551, "etc"
end
entries.uniq!
open fstab, "w" do |f|
entries.each do |e|
f.puts "/#{e} #{d}/rootfs/#{e} none ro,bind 0 0"
end
end
open lxcconf, "w" do |f|
f.puts <<END
# empty network is currently buggy.
lxc.network.type = veth
lxc.rootfs = #{rootfs}
lxc.mount = #{fstab}
lxc.cgroup.devices.deny = a
END
end
system 'lxc-execute',
'-n', dir.basename, '-f', lxcconf,
'--', sudo, '-u', 'nobody', *ARGV
end