Micro Hardeningv2に参加した話(2022/5/21)
参加して,ぼこぼこにされた話.
きっかけ
上司命令でconnpassのイベントに参加してきて腕を磨いて来いと言われた.
昔トラウマになったCTF Attack&Defenderに似たMicro Hardeningが楽しそうだったから.
競技概要
こんなマニアックなブログを見ているのは競技を知っている人だろうから触りだけ.
各チーム(最高6名)にサーバが割り当てられ,その上で動いているサービス(ショッピング,ブログ,サイト)の運用監視し,脆弱性攻撃やらを防ぐ.
チームとしてやったこと
私は主にログ監視していた.multitailとaliasのコンビが非常に秀逸.
- 脆弱なパスワード変更
- ファイルバックアップ
...etc
トラウマ
- 気づいたらHP書き換えられている
- 気づいたら売り上げ落ちている
- 不正ログインのオンパレード
- ログが静かだと思ったら上記全てが終わっていた・・・
反省
- パスワード変更対象が多すぎて漏れていた
- 最低限のアクセス制限とユーザ自体の完全遮断のバランスが難しい(悪いユーザも善行をすることがある)
次回やりたいこと
所感
攻撃が来たらヒャッハーって叫びながらログをつなぎ合わせて攻撃者と内容を見るのが楽しかった.
競技の最後にあった大規模障害の対応方法や障害対応にとって大切なことは非常に参考になった.
slack/discord,Guacamoleと知らないものも触れられてよかった.
SSH接続のみやってたのでGUI画面も次回は触ってみたい.
主催者の川口さんやその後のまったり運営されていた方々,ありがとうございました!
反省を繰り返しながら9回ほど参加すればほぼ満点取れるという伝説もあるようなのでまた出たい.
(参考)結果
team3メンバーがサーバに強い人達だったので初参加にしては結構良い得点だった.
参考文献
team member: Micro Hardening v2に参加した話
connpss: Micro Hardening - connpass
qemu上でLinuxカーネルを動かしたらエラーメッセージが止まらない問題への調査結果と対処法
qemu上にて自分でビルドしたカーネルを動かしたらエラーメッセージが止まらない.
これに対処するために原因調査,対処方法を探した話.
結果的に対処法は見つかったが,根本的な原因追求はできなかった.
知っている人がいれば情報募集中.
対象者
この記事はqemuでLinuxカーネルを動かしたときに下記のようなメッセージが何度も出続ける現象に苦しむ人を対象とする.
process '-/bin/sh' (pid 15) exited. Scheduling for restart. can't open /dev/tty3: No such file or directory
TL;DR
- エラーメッセージの原因はinitrd(initramfs)内にあることまで突き止めた
- vagrantのubuntu(恐らくServer版)のinitrdを使えば問題を回避できた
(initrdを替えられない事情がある人には無用な記事かも)
何があった?
カーネルを自分でビルドして,qemu上で起動したら下記エラーメッセージが止まらない.
メッセージが出すぎてまともにコマンド入力できない.
process '-/bin/sh' (pid 15) exited. Scheduling for restart. process '-/bin/sh' (pid 16) exited. Scheduling for restart. process '-/bin/sh' (pid 17) exited. Scheduling for restart. can't open /dev/tty3: No such file or directory can't open /dev/tty4: No such file or directory can't open /dev/tty2: No such file or directory
ググってみると下記コマンドで改善するという情報があった.
$ ln -sf /dev/console /dev/tty3
しかしメッセージが多すぎてコマンドが打てないため,他の方法を探す必要があった.
他の記事を見てもOSアップデートで改善したとかしか出ず,原因まで書いたものを見つけられなかった.
だから自分で調べることにした.
環境構築
まずは今回の現象が再現する環境構築方法を記し,その後回避策を説明する.
手っ取り早く解決したい人はinitrdの用意
まで読み飛ばすこと.
エラーメッセージにたどり着くためにまずは起動用のカーネルイメージ(bzImage
)をビルドする必要がある.
bzImageのビルド
カーネルは全てビルドすると20GB以上のストレージと8GB以上のメモリ,数時間は必要となり自分の手持ちPCではできない.
ビルド中は数時間掛けてじわじわ使用メモリが増えていき,気づいたときはメモリ切れで強制終了,数時間が水の泡となるのは心に来る(経験談).
しかし考えてみればUbuntuなどでは起動に必要なvmlinuz
は20GBも必要ない.
最小構成であればそこまで容量も必要ないのではないかと考えた.
bzImage
があれば最低限起動できるようだったため,シンプルにビルドする方法を選んだ.
カーネルのビルド方法は下記サイトを参考にした.
minimalなlinuxを作りたい、と思ったんだ - livaの雑記帳
Linux を(わりと)シンプルな構成でビルドして Qemu で起動する - Qiita
ビルドしたLinuxカーネルをブートできる最低限の環境を用意する(with Busybox & qemu) - 豆腐の豆腐和え
ビルド手順
このサイトからソースを持ってきた.
使ったバージョンは5.9.1
で今は公開されていないが,5.9系
ならそこまで違いは無いかと思う.
そこからはポチポチコマンドを実行していく.
make
するために必要なパッケージがあるかもしれないから,それはエラーメッセージを参照しながら補完してほしい.
$ tar xvf linux-5.9.1.tar.xz $ cd linux-5.9.1 $ make allnoconfig $ make menuconfig [*] 64-bit kernel -> General setup [*] Initial RAM filesystem and RAM disk (initramfs/initrd) support -> General setup -> Configure standard kernel features [*] Enable support for printk -> Executable file formats / Emulations [*] Kernel support for ELF binaries -> Device Drivers -> Character devices [*] Enable TTY -> Device Drivers -> Character devices -> Serial drivers [*] 8250/16550 and compatible serial support [*] Console on 8250/16550 and compatible serial port $ make -j4 bzImage $ find ./ -name bzImage ./arch/i386/boot/bzImage ./arch/x86_64/boot/bzImage ./arch/x86/boot/bzImage $ mkdir work $ cp ./arch/x86_64/boot/bzImage work $ cd work
これでbzImage
の作成は完了.
参考サイトでは起動できるところまでだったり(カーネルが起動し,panicするところまで),busyboxの用意があったりでややこしい.
ちゃんとしたinitrd
を用意してやれば小さなLinuxとして使えるようだ.
というわけで今回使ったinitramfs-linux.img
は普段使っているLinux(Arch Linux, Kernel5.9.11-arch2-1)から抜き出した.
$ cp /boot/initramfs-linux.img work/initramfs-linux.img $ qemu-system-x86_64 -kernel ./bzImage -initrd ./initramfs-linux.img -nographic -append "console=ttyS0"
実行後ブートメッセージが出力されて起動する.
panicも起こらない.
やった!悲劇はここからだ!!
エラーメッセージが止まらない・・・
例のエラーメッセージ
process '-/bin/sh' (pid 18) exited. Scheduling for restart. process '-/bin/sh' (pid 19) exited. Scheduling for restart. process '-/bin/sh' (pid 20) exited. Scheduling for restart. can't open /dev/tty2: No such file or directory can't open /dev/tty3: No such file or directory can't open /dev/tty4: No such file or directory
調査する前にこの状態だとまともにコマンド入力もできないから,ホスト側から他のシェルを使ってqemu
を停止.
$ kill -KILL `ps aux | grep qemu | awk -F" " '{print $2}' | head -n1`
調査
メッセージ内容からすると/dev/tty(2,3,4)
が無いからメッセージが出力されているように見られる.
まず調べるべきはttyデバイスが作成されるタイミング.
怪しいのは
辺りかと思い,詳細を調べた.
補足するとinitrd
は起動時に読み込まれるファイルシステム,initrd
の中にある最初に実行されるプログラムがinittab/systemd
となる.
昔はinittab
だったようだが,今はsystemd
がこの役割を担っているようだ.
/sbin/init
がsystemd
へのリンクになっていたのを知ったときは訳がわからなかった.(感想)
しかしここで行き詰まった.
ttyデバイスはシェルで接続した瞬間作成されるようだ.
よって問題のメッセージを読み解くと
- 自動で実行されるプロセスが
/bin/sh
を実行 - 異常終了
- 再実行がスケジュール
/dev/tty
がなくて/bin/sh
が異常終了するのか,他の要因があるのかはわからない.
このプロセスがスケジューリングされるのはinitrd内にあるinittab/systemd
で自動的に時実行されるプロセス(inittabならrc.S,systemdならtarget)と推察される.
つまり問題となるプロセスを登録していないinitrd
があれば問題のメッセージは出力されないのではないか.
というわけでvagrantのubuntu(Server)からinitramfs.img
を取り出して試してみる.
ubuntuを選んだ理由はCUI起動のみという点でビルドしたカーネルイメージと環境が近いと考えたから.
initrdの用意(回避策はここから)
まずは初期設定.
$ vagrant init ubuntu/trusty64
作成されたVagrantfile
にファイル取り出し用の共有フォルダを設定する.
config.vm.synced_folder
の箇所を編集する.
ルールはconfig.vm.synced_folder <ホストのディレクトリパス> <ゲスト内でのディレクトリパス>
.
パスは各自の環境を入力.
SHELL
の箇所に編集されたところがあるが,今回とは関係ないからコメントアウトのままでOK.
# -*- mode: ruby -*- # vi: set ft=ruby : # All Vagrant configuration is done below. The "2" in Vagrant.configure # configures the configuration version (we support older styles for # backwards compatibility). Please don't change it unless you know what # you're doing. Vagrant.configure("2") do |config| # The most common configuration options are documented and commented below. # For a complete reference, please see the online documentation at # https://docs.vagrantup.com. # Every Vagrant development environment requires a box. You can search for # boxes at https://vagrantcloud.com/search. config.vm.box = "ubuntu/trusty64" # Disable automatic box update checking. If you disable this, then # boxes will only be checked for updates when the user runs # `vagrant box outdated`. This is not recommended. # config.vm.box_check_update = false # Create a forwarded port mapping which allows access to a specific port # within the machine from a port on the host machine. In the example below, # accessing "localhost:8080" will access port 80 on the guest machine. # NOTE: This will enable public access to the opened port # config.vm.network "forwarded_port", guest: 80, host: 8080 # Create a forwarded port mapping which allows access to a specific port # within the machine from a port on the host machine and only allow access # via 127.0.0.1 to disable public access # config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1" # Create a private network, which allows host-only access to the machine # using a specific IP. # config.vm.network "private_network", ip: "192.168.33.10" # Create a public network, which generally matched to bridged network. # Bridged networks make the machine appear as another physical device on # your network. # config.vm.network "public_network" # Share an additional folder to the guest VM. The first argument is # the path on the host to the actual folder. The second argument is # the path on the guest to mount the folder. And the optional third # argument is a set of non-required options. config.vm.synced_folder "/home/<user>/kernel-5.9.1/work/", "/shared" # Provider-specific configuration so you can fine-tune various # backing providers for Vagrant. These expose provider-specific options. # Example for VirtualBox: # # config.vm.provider "virtualbox" do |vb| # # Display the VirtualBox GUI when booting the machine # vb.gui = true # # # Customize the amount of memory on the VM: # vb.memory = "1024" # end # # View the documentation for the provider you are using for more # information on available options. # Enable provisioning with a shell script. Additional provisioners such as # Ansible, Chef, Docker, Puppet and Salt are also available. Please see the # documentation for more information about their specific syntax and use. # config.vm.provision "shell", inline: <<-SHELL # yes | apt install autoconf bison libtool automake autopoint pkg-config # SHELL end
編集が終わったら起動する.
$ vagrant up $ vagrant ssh (vagrant)$ uname -a Linux vagrant-ubuntu-trusty-64 3.13.0-170-generic #220-Ubuntu SMP Thu May 9 12:40:49 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux (vagrant)$ cp /boot/initrd.img-3.13.0-170-generic /shared
これで指定した共有フォルダ内にimgファイルが保存される.
保存したimgを使ってカーネルイメージを再度実行してみる.
オリジナルのままだと名前が長いから改名した.
$ mv ./initrd.img-3.13.0-170-generic initrd_ubuntu.img $ qemu-system-x86_64 -kernel ./bzImage -initrd ./initrd_ubuntu.img -nographic -append "console=ttyS0"
メッセージは出なくなった!
結論
問題のエラーメッセージはinitrd
内(inittab/systemd
が有力)に登録されたプロセスが原因と推測される.
問題となるプロセスが登録されていないinitrd
があればメッセージを回避できる.
所感
カーネルをビルドして使うなら用途に応じてinitrd
も作り込まないといけないってことか.
参考
linux - can't open /dev/tty(2,3,4). No such file or directory - Stack Overflow
minimalなlinuxを作りたい、と思ったんだ - livaの雑記帳
Linux を(わりと)シンプルな構成でビルドして Qemu で起動する - Qiita
ビルドしたLinuxカーネルをブートできる最低限の環境を用意する(with Busybox & qemu) - 豆腐の豆腐和え
PCのBluetoothバージョンを確認するコマンド(Arch Linuxを例に)
Bluetooth 5.0は速いと聞く.
でもPC・イヤホンなどのBluetooth機器が両方ハード・ソフト的に対応していないと使えないらしい.
ソフトはアップグレードできるから,とりあえずPCのハードはどのバージョンなのかを確かめたメモ.
コマンド郡
コマンドの流れは以下の通り.
1. bluetooth関連のパッケージ(bluez
, bluez-utils
)をインストール
2. buetoothデーモンを実行(modprobe
, systemctl
)
3. bluetoothデバイスを有効化(対話式コマンドbluetoothctl
で操作)
4. 3.で起動したbluetoothデバイスの詳細情報からバージョンを確認
恥ずかしいからMACアドレスは?
にしてある.
# 1.bluetooth関連のパッケージ(`bluez`, `bluez-utils`)をインストール $ sudo pacman -S bluez bluez-utils # 2. buetoothデーモンを実行(`modprobe`, `systemctl`) $ sudo modprobe btusb $ sudo systemctl start bluetooth # デーモン実行 $ sudo systemctl enable bluetooth # サービスの有効化 # 3. bluetoothデバイスを有効化(対話式コマンド`bluetoothctl`で操作) $ which bluetoothctl # コマンド有無 $ bluetoothctl help # コマンドの確認 $ bluetoothctl # 対話式 Agent registered [CHG] Controller ??:??:??:??:??:?? Pairable: yes [bluetooth]# agent KeyboardOnly [bluetooth]# default-agent [bluetooth]# power on [CHG] Controller ??:??:??:??:??:?? Class: 0x000c010c Changing power on succeeded [CHG] Controller ??:??:??:??:??:?? Powered: yes [bluetooth]# scan on [bluetooth]# devices # 近くにあるデバイス一覧表示(何らかのデバイスが出てくればOK) [bluetooth]# quit # 4. 3.で起動したbluetoothデバイスの詳細情報からバージョンを確認 $ hciconfig -a hci0: Type: Primary Bus: USB BD Address: ??:??:??:??:??:?? ACL MTU: 1021:4 SCO MTU: 96:6 UP RUNNING PSCAN ISCAN RX bytes:15896 acl:0 sco:0 events:2476 errors:0 TX bytes:596459 acl:0 sco:0 commands:2466 errors:0 Features: 0xff 0xfe 0x0f 0xfe 0xdb 0xff 0x7b 0x87 Packet type: DM1 DM3 DM5 DH1 DH3 DH5 HV1 HV2 HV3 Link policy: RSWITCH HOLD SNIFF Link mode: SLAVE ACCEPT Name: '????????????' Class: 0x0c010c Service Classes: Rendering, Capturing Device Class: Computer, Laptop HCI Version: 4.2 (0x8) Revision: 0x100 LMP Version: 4.2 (0x8) Subversion: 0x100 Manufacturer: Intel Corp. (2)
最後のコマンド結果にあるLMP Version: 4.2...
というところがBluetoothのバージョンに当たる.
LMP (Link Manager Protocol) のバージョンとBluetoothの対応付は以下の通り.
LMP (Link Manager Protocol) バージョン | Bluetooth コア仕様 |
---|---|
LMP 0 | Bluetooth コア仕様 1.0b (使用中止) |
LMP 1 | Bluetooth コア仕様 1.1 (使用中止) |
LMP 2 | Bluetooth コア仕様 1.2 (使用中止) |
LMP 3 | Bluetooth コア仕様 2.0 + EDR (使用中止) |
LMP 4 | Bluetooth コア仕様 2.1 + EDR (非推奨、使用中止予定) |
LMP 5 | Bluetooth コア仕様 3.0 + HS (非推奨、使用中止予定) |
LMP 6 | Bluetooth コア仕様 4.0 |
LMP 7 | Bluetooth コア仕様 4.1 |
LMP 8 | Bluetooth コア仕様 4.2 |
LMP 9 | Bluetooth コア仕様 5.0 |
LMP 10 | Bluetooth コア仕様 5.1 |
LMP 11 | Bluetooth コア仕様 5.2tel Corp. (2) |
今回自分のPCは4.2
.つまりBluetooth コア仕様 2.1 + EDR (非推奨、使用中止予定)
.
最新機種のイヤホンなどはほぼその性能を活かせない.ウソみたいだろ?
所感
Bluetooth機器を買う夢は始まる前から終わっていた.
LMPの対応表を見つけるまではLMP=Bluetooth
でなんだ4.2
対応機種を探せばいいのかーくらいに軽く考えていた.
もしかして最新のAirPods
が接続できなかったのはこれが原因だろうか・・・.
調べた感じだとプロファイルがきっちりしていれば接続自体はできるとあったが,それでも高速伝送などの面で劣ることに変わらない.
次の手段を考えるかー.
まだできることはあるはずだ.
参考
最新のBluetooth規格とアップグレード方法 | ゲーミングPC辞典
https://support.microsoft.com/ja-jp/help/4524769/windows-10-what-bluetooth-version-is-on-my-device
情報系カンファレンス格付けサイトCOREを知った話
カンファレンスの格付けが素早く調べられるCOREを知った話.
背景
研究の流行やState Of The Art(SOTA)を知りたいときはトップカンファレンスの論文を探すことから始めるようにしている.
限られた時間で質の良い論文をすばやく探すのは重要だ.
課題
論文を探す上で以下の課題に悩まされていた.
- 無数にあるカンファレンスの中でどれがトップなのか悩む
- 効率が悪い(検索アルゴリズムはbrute force, O(n)?)
(とりあえず知っているACM CCSの論文で参照されている会議の論文をあたる
ACMの公式サイトにあるカンファレンスを総当り)
インパクトファクターもあるけど生物系の評価基準でコンピュータ・サイエンスには使いにくい気がする(個人の意見です).
格付けサイトを使う利点
今回紹介するCORE(に限らず)格付けサイトを使えば以下の利点がある.
- 無数にあるカンファレンスの中でどれがトップなのか悩む
→ トップカンファレンス(格付けサイト内で評価が高い)の把握ができる
質の良い論文が揃っているカンファレンスを素早く把握できる - 効率が悪い(検索アルゴリズムはbrute force)
→ 調べる対象を減らすことができる(O(n)的なのは変わらないかも・・・)
上記に加えて自分が知らない領域でも読むべきカンファレンスの絞り込みができる.
Computing Research & Education(CORE)
本題.
Computing Research & Education(以降CORE)というサイトを紹介.
オーストラリアとニュージーランドの大学により運営されている.
コンピュータ・サイエンスに特化しており,分野を限って調べたいときは都合が良い.
ただし,Unrankedなカンファレンスも多いため参考にできないこともある.
Documentも2020年版が掲載されているため,情報も新しそうだ.
格付け基準
カンファレンスの格付けは*A/A/B/C/unranked
.
詳細はここに記載.
迷ったら*A/A
を当たれば良さげ.
内部ではJournal Citation ReportsとScopusの評価を参考にしているようだ.
使い方
ここに会議名を入れるだけ.
dimva
ってトップカンファレンスと思っていたけどC
評価だった.
ええ・・・?
所感
格付けサイトの回し者みたいな記事になってしまった・・・.
調べはじめの方針決めに使うのが良さげ.
実際は他の分野も総括して調べられるScopus(使ったことはない)を使うのが良いのかな.
余談になるが,会計処理も公開していた.
2019年は$45,234.21
, 日本円に換算すると480
万円.
サイト運営も楽じゃないようだ.
参考
Filesystem Hierarchy Standardを読む
Linuxに出会ってから10年ほど経つが標準文書を読んだことはなかった.
まずはファイルシステムについて規定されたFilesystem Hierarchy Standardを理解がてらまとめる.
適宜省略しているため必要に応じて原文を読んでください.
Linuxの標準について
Linuxの規定はLinux Standard Base(以後LSB)
に記載されている.
目的: LinuxディストリLSBの目標は、Linuxディストリビューション間での互換性を向上させ、準拠システム上でのアプリケーションの動作を保証するよう標準規格を策定・振興することである。さらに、ソフトウェアベンダーがLinux向けに製品を移植したり開発する際の調整努力を助ける。
(Wikipediaより)
最新は5(2020/05/17現在)
.Coreドキュメントは964ページある.
基本的なライブラリlibc
関係とかよくしらなかったがそんな疑問もこれを読めばとっかかりとなりそう.
Filesystem Hierarchy Standard
ようやく本題.3.0
(50 page)を読み進めていく.
Linuxの必要なファイル・ディレクトリ配置を記載した文書.
目的はアプリケーション,管理ツールなどの互換性を図るもの.
文書の目的(Chapter1 Introduction Purpose)
ユーザ・ソフトがインストールされているファイルやディレクトリの場所を予想できるようにすること.
Root Filesystem(Chapter3)
root filesystem
はboot/restore/recover/repairのために必要なものを格納する.
必要なディレクトリ一覧
ディレクトリ名 | 詳細 |
---|---|
bin | 必須コマンドバイナリ置き場 |
boot | ブートローダ用の静的ファイル |
dev | デバイスファイル |
etc | ホスト特有の設定ファイル |
lib | 必須共有ライブラリとカーネルモジュール |
media | removable meidaのマウントポイント |
mnt | 一時的なファイルシステムのマウントポイント |
opt | Add-onアプリ置き場 |
run | running processesのデータ関連 |
sbin | 必須システムバイナリ |
srv | システムから提供されるサービスのデータ |
tmp | 一時ファイル |
usr | Secondary Hierarchy |
var | Variable data |
以下,オプション | |
home | ユーザ用ホームディレクトリ |
lib |
必須な共有ライブラリの代替 |
root | root用のホームディレクトリ |
bootableなLinuxを作ろうと思ったら,最低限上記のディレクトリを揃える必要があるということか.
ここでは省略するが,各ディレクトリの詳細も記載されている.
/usr/share/color
とか初耳なディレクトリもある.
/var Hierarchy(Chapter5)
可変データファイル置き場. スプールディレクトリ,ファイル,管理ログも含まれる.
必要なディレクトリ一覧
ディレクトリ名 | 詳細 |
---|---|
cache | キャッシュデータ |
lib | 状態情報 |
local | /usr/localの情報 |
lock | lock files |
log | ログファイル関連 |
opt | /opt用の情報 |
run | running processes関連のデータ |
spool | アプリのspool data(syorimati data) |
tmp | 一時ファイル |
以下,オプション | |
account | process accounting logs |
crach | system crash dumps |
games | variable game data |
user mailbox files | |
yp | Network Information Service database files |
/var/lock
は複数のアプリで共有されているデバイスやリソースを管理するために使われる.
所感
文書によっては2770ページという頭が真っ白になりそうな量もあるが,調べたい領域を絞れば数十ページで済む.
読み始めてしまえばわかりやすい英語でスラスラ読めた.
今回読んだFHSには各ディレクトリ全てについて何に使うために存在するかが書かれている.
何度も繰り返し読みながら理解を進めていきたい.
spool
は処理待ちの一時ファイルを格納する場所初めて知ることもあってまだまだ勉強不足だと思い知った.
kernel開発者になる道は遠い.
一歩々々進んでいきたい.
参考
VagrantでRust製デバドラを動かす環境を作る話
CTF問題でもない限り好んでUse After Freeバグを作りたくはない.
その点Rust(ラストと読むと最近知った)はC/C++に比べてメモリ管理について優れていると言われている.
低レベル操作も充実していて速度もC/C++並に出るときもある.
Rustのチュートリアルを一通り読み終わったのでVagrant上でデバドラ開発環境を整えた.
やりたかったこと
よりバグを少なくデバドラ開発がしたい!
Goodbye, unsafe C/C++!
ヒャッハー,開発中のバグでホストがカーネルパニックしたくないから仮想環境の中に閉じ込めだぁ!
ステップ
C言語でも確認するのは必要なモジュールがインストールできているかの確認用.
Rust製のHello, Worldなデバドラはこれを使った.
Dockerで環境を作ることも考えたが,以下の点で向いていないと判断.
やるまえから嫌な予感はしつつ挑戦してみた.
「上記の問題解決を頑張るのが目的じゃないから違う方法を考えよう.」
そう気づいた頃にはDockerイメージ作成してから3時間経っていた.もっと早く気づけ.
今回はUbuntuのイメージで環境を作っていったが,Ubuntuの標準レポジトリからインストールできるlinuxソースのバージョンとホストOSのバージョンが違うと本当に面倒くさい.
レポジトリ追加とかやったら解決できるけどdmesg
できない問題が口を開けて待っている.
良い子のみんなはフルエミュレーションな仮想環境が一番と心に刻んでほしい.
環境構築
Vagrantのインストールは探せばいくらでも出てくるから省略する.
$ sudo pacman -S vagrant
的なことをすれば良い.
$ vagrant --version Vagrant 2.2.6
Vagrantを立ち上げる
以下をVagrantfile
として保存.
# frozen_string_literal: true # -*- mode: ruby -*- # vi: set ft=ruby : # All Vagrant configuration is done below. The "2" in Vagrant.configure # configures the configuration version (we support older styles for # backwards compatibility). Please don't change it unless you know what # you're doing. Vagrant.configure('2') do |config| # The most common configuration options are documented and commented below. # For a complete reference, please see the online documentation at # https://docs.vagrantup.com. # Every Vagrant development environment requires a box. You can search for # boxes at https://vagrantcloud.com/search. config.vm.box = 'generic/ubuntu1810' # Disable automatic box update checking. If you disable this, then # boxes will only be checked for updates when the user runs # `vagrant box outdated`. This is not recommended. # config.vm.box_check_update = false # Create a forwarded port mapping which allows access to a specific port # within the machine from a port on the host machine. In the example below, # accessing "localhost:8080" will access port 80 on the guest machine. # NOTE: This will enable public access to the opened port # config.vm.network "forwarded_port", guest: 80, host: 8080 # Create a forwarded port mapping which allows access to a specific port # within the machine from a port on the host machine and only allow access # via 127.0.0.1 to disable public access # config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1" # Create a private network, which allows host-only access to the machine # using a specific IP. # config.vm.network "private_network", ip: "192.168.33.10" # Create a public network, which generally matched to bridged network. # Bridged networks make the machine appear as another physical device on # your network. # config.vm.network "public_network" # Share an additional folder to the guest VM. The first argument is # the path on the host to the actual folder. The second argument is # the path on the guest to mount the folder. And the optional third # argument is a set of non-required options. config.vm.synced_folder './', '/vagrant_data' # Provider-specific configuration so you can fine-tune various # backing providers for Vagrant. These expose provider-specific options. # Example for VirtualBox: # # config.vm.provider "virtualbox" do |vb| # # Display the VirtualBox GUI when booting the machine # vb.gui = true # # # Customize the amount of memory on the VM: # vb.memory = "1024" # end # # View the documentation for the provider you are using for more # information on available options. # Enable provisioning with a shell script. Additional provisioners such as # Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the # documentation for more information about their specific syntax and use. # config.vm.provision 'shell', inline: <<-SHELL # apt update # apt install -y install git gcc curl make kmod linux-source linux-headers-$(uname -r) build-essential libelf-dev # apt install llvm clang # SHELL end
$ vagrant init
で生成されるファイルほぼそのまま.
いじったのはconfig.vm.box
にUbuntu 18.10を選択したこととホストとの共有フォルダとしてVagrantfile
があるディレクトリを/vagrant_data
としてマウントする設定を加えた.
SHELL
は無効化中のため割愛.
Vagrant環境内に開発環境を持ち込むと設定がまた時間がかかるから共有フォルダ経由で編集はホスト,コンパイル・検証は仮想環境内に切り分けた.
起動してログインする.
$ vagrant up $ vagrant ssh vagrant@ubuntu1810:/$
環境とバージョンは以下の通り.
$ uname -a Linux ubuntu1810.localdomain 4.18.0-25-generic #26-Ubuntu SMP Mon Jun 24 09:32:08 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
まずは必要なパッケージをインストール.
$ apt-get update $ apt-get install -y install git gcc curl make kmod linux-source linux-headers-$(uname -r) build-essential libelf-dev $ ls /lib/modules/`uname -r`/build # 必要なものがある確認 arch crypto firmware init Kconfig Makefile net security tools virt block Documentation fs ipc kernel mm samples sound ubuntu zfs certs drivers include Kbuild lib Module.symvers scripts spl usr $ gcc --version gcc (Ubuntu 8.3.0-6ubuntu1~18.10.1) 8.3.0 $ make --version GNU Make 4.2.1
作業用ディレクトリ(共有フォルダ)に移動.
$ cd /vagrant_data $ ls Vagrantfile
これで最低限の環境は整った.
C言語のHello, Worldなデバドラのコンパイル・実行
C言語でのデバイスドライバがコンパイル・実行できるかを確認する.
ソースコードとMakefile
を用意する.
// hello.c #include <linux/module.h> MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("poppycompss <t0g0v31dk@gmail.com>"); MODULE_DESCRIPTION("Hello world kernel module"); static int km_init(void) { printk(KERN_INFO "Hello my module\n"); return 0; } static void km_exit(void) { printk(KERN_NOTICE "Goodbye my module\n"); } module_init(km_init); module_exit(km_exit);
起動時にHello my module
, 終了時にGoodbye my module
と出力するだけのデバドラ.
続いてMakefile
# Makefile .PHONY: all clean obj-m := hello.o KDIR := /lib/modules/$(shell uname -r)/build PWD = $(shell pwd) all: make -C ${KDIR} M=$(PWD) modules clean: make -C ${KDIR} M=$(PWD) clean
$ cp /lib/modules/`uname -r`/build/.config ./ $ cp /lib/modules/`uname -r`/build/Module.symvers ./
準備はここまで.コンパイルする.
$ make make -C /lib/modules/4.18.0-25-generic/build M=/vagrant_data modules make[1]: Entering directory '/usr/src/linux-headers-4.18.0-25-generic' CC [M] /vagrant_data/hello.o Building modules, stage 2. MODPOST 1 modules LD [M] /vagrant_data/hello.ko make[1]: Leaving directory '/usr/src/linux-headers-4.18.0-25-generic'
hello.ko
が生成されているはず.
$ sudo insmod hello.ko $ sudo rmmod hello $ dmesg | tail [21061.045821] Hello my module [21061.054198] Goodbye my module
これでC言語のデバドラが動いていることが確認できた.
次からが本題.
Rust製のHello, Worldなデバドラのコンパイル・実行
必要なパッケージのインストール
$ sudo apt install -y llvm clang $ clang -v clang version 7.0.0-3 (tags/RELEASE_700/final) Target: x86_64-pc-linux-gnu ...(snip)...
Rustをローカルにインストールする.
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
もし上記がうまく行かなければ
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > rust-installer.sh $ chmod +x ./rust-installer.sh $ ./rust-intsaller.sh
プロンプトで「どれをインストールする?」って聞かれたら'1'を入れた.
インストールが終わったらパスを通す
$ source $HOME/.cargo/env
これでrustup
コマンドが使えるようになる.
今回動かそうとしているデバドラにはRustのnightly
を使う必要があるからインストールして切り替える.
切り替えはデバドラの作業ディレクトリで実施した.
$ rustup install nightly $ git clone https://github.com/fishinabarrel/linux-kernel-module-rust $ cd linux-kernel-module-rust/hello-world $ rustup toolchain list # 現在使っているバージョンを確認 stable-x86_64-unknown-linux-gnu (default) nightly-x86_64-unknown-linux-gnu $ rustup override set nightly # nightlyに切り替え $ rustup component add rust-src rustfmt $ rustup --version rustc 1.41.0 (5e1a79984 2020-01-27)
ようやくmake
できると思いきやいろんなエラーにぶつかる (ぶつからなかった人はそのまま次に進んでください)
$ make // エラーのみ抜粋 error[E0308]: mismatched types --> /vagrant_data/linux-kernel-module-rust/src/allocator.rs:13:41 | 13 | bindings::krealloc(ptr::null(), layout.size(), bindings::GFP_KERNEL) as *mut u8 | ^^^^^^^^^^^^^ expected `u64`, found `usize` | help: you can convert an `usize` to `u64` and panic if the converted value wouldn't fit | 13 | bindings::krealloc(ptr::null(), layout.size().try_into().unwrap(), bindings::GFP_KERNEL) as *mut u8 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error[E0308]: mismatched types --> /vagrant_data/linux-kernel-module-rust/src/file_operations.rs:193:28 | 193 | self.0.read = Some(read_callback::<T>); | ^^^^^^^^^^^^^^^^^^ expected `u64`, found `usize` | = note: expected fn pointer `unsafe extern "C" fn(_, _, u64, _) -> i64` found fn item `unsafe extern "C" fn(_, _, usize, _) -> isize {file_operations::read_callback::<T>}` error[E0308]: mismatched types --> /vagrant_data/linux-kernel-module-rust/src/file_operations.rs:200:29 | 200 | self.0.write = Some(write_callback::<T>); | ^^^^^^^^^^^^^^^^^^^ expected `u64`, found `usize` | = note: expected fn pointer `unsafe extern "C" fn(_, _, u64, _) -> i64` found fn item `unsafe extern "C" fn(_, _, usize, _) -> isize {file_operations::write_callback::<T>}` error[E0308]: mismatched types --> /vagrant_data/linux-kernel-module-rust/src/sysctl.rs:136:36 | 136 | proc_handler: Some(proc_handler::<T>), | ^^^^^^^^^^^^^^^^^ expected `u64`, found `usize` | = note: expected fn pointer `unsafe extern "C" fn(_, _, _, *mut u64, _) -> _` found fn item `unsafe extern "C" fn(_, _, _, *mut usize, _) -> _ {sysctl::proc_handler::<T>}` error: aborting due to 4 previous errors For more information about this error, try `rustc --explain E0308`. The following warnings were emitted during compilation: error: could not compile `linux-kernel-module`.
bindgen
のバージョンによる変更の影響らしい.
Cargo.toml
を修正する.
[-] bindgen = "*" [+] bindgen = "0.51.0"
$ make
コンパイルが成功したら読み込んでみる.
$ sudo insmod helloworld.ko $ sudo rmmod helloworld $ dmesg | tail ...(snip)... [40960.460642] Hello kernel module! [40960.468459] My message is on the heap! [40960.468460] Goodbye kernel module! ...(snip)...
動いた.
所感
docker
はデバドラ開発には向いてない(確信)
実はRust製デバドラとしてはよりシンプルなものもあるがこっちもエラーにぶつかった.
今後の開発を考えると今回使ったやつのほうが実装されている部分が多くて参考になりそうだったからデバッグはしてない.
開発は慣れるまでCで書いて動作確認しつつRustに書き直す作業になりそうだ.
参考
「Rust」言語はCよりも遅いのか、研究者がベンチマーク結果を解説:モダンCPUでは性能低下は軽微 - @IT
GitHub - tsgates/rust.ko: A minimal Linux kernel module written in rust.
Linux環境でのインストールスクリプト確認用にdocker-composeを整備する
開発したプログラムが複数の環境上で動くかどうかの確認をしたいときがある.
例えばUbuntu
, CentOS
はバージョン・ディストリの特徴によって環境が僅かに違うが,新しいOSをインストールした際の設定自動化スクリプトはどんな場所でも動いてほしい.
例えば自分が作成したプログラムのコンパイル・インストールをどんな環境でも動くスクリプト1つ実行する形にまで仕上げたいとき
しかしこれを全環境の仮想マシンを用意してチェックしていくのは面倒だ.
というわけでdocker-compose
を使って一斉に各ディストリで動くかどうかをチェックする環境を作った話.
やりたかったこと
最初に書いた理由も再掲.
- 作ったプログラムが各ディストリ・バージョン上で動くかどうか簡単に確かめたい
- 開発環境で動くようになったがまっさらな環境で動くのか確認したい
- 上記を一斉に行いたい
とうわけでコンパイル・インストールを確認する環境を作り,自分の環境構築用スクリプトが動くかどうかを確かめた.
何を使って実現するか
他の人がどうやっているのか知らないがいくつか案を練った.
- Virtualboxを複数立てて(
vagrant
を使う?)SSHで同時接続 - コンテナ(
docker-compose
,Kubernetes
)を使う - ラズパイをたくさん買ってPCを複数接続できるキーボードを用意して頑張る(ド鬼畜)
3.は財布的・手間的にない.1.は理想的だが手間がかかる.
2.が現実的かな.
docker-compose
とKubernetes
を比較すると後者のほうが大規模の構成ができそうだ.
が,今回の用途だとそこまで複雑な実装は不要そう.
zozoのブログが非常に刺激になり,現実的な実装を思いつけた.
考えたアーキテクチャ
考慮した結果こうなった.
開発者は端末から最初コマンドを実行すればあとはコンテナを立ち上げ,各コンテナ内での標準入出力をログとしてホスト上に保存する.
ファイル構成はこんな感じ.
|--dists # 各ディストリ設定を置く | |--ubuntu | | |--Dockerfile | | |--personal-settings.yml |--docker-compose.yml # 共通設定 |--.env # 環境変数 |--logs # 各コンテナからのログの保存先 | |--ubuntu.log # ubuntuコンテナからのログ(dists/???は???.logというファイルを作る) |--run_env_test.sh # テスト実行
コンテナの追加はシンプルにしたかった.
考えた結果,追加はdists
以下にコンテナ用の設定ディレクトリを追加するだけで良いようにした.
$ cp dists/ubuntu dists/centos
とかして,中のファイルをいじれば良い.
各ファイルの中身
dists/<ディストリ>/Dockerfile
例はubuntu
の場合で説明.
FROM ubuntu # 好きなコンテナを指定.他の設定もお好みで追加.
dists/<ディストリ>/personal-settings.yml
ubuntu
の場合.
各コンテナ固有の設定を書く.
ホスト上のディレクトリをマウントして共有している.
command
のところに実行したいコマンドを入力する.
今入力されているのは環境構築用のスクリプト.
最後のsleep
コマンドはエラーが合った時にデバッグするためにbash
を継続させる.
version: "3.7" services: ubuntu: image: ubuntu tty: true container_name: 'ubuntu' environment: APP_ENV: ubuntu volumes: - ./dists/ubuntu/Dockerfile:/mnt/host/Dockerfile:ro - ./dists/ubuntu/personal-settings.yml:/mnt/host/personal-settings.yml:ro - ./logs:/mnt/host/logs command: bash -c 'apt -y update > /mnt/host/logs/ubuntu.log && apt -y install git >> /mnt/host/logs/ubuntu.log && git clone https://github.com/poppycompass/dotfiles >> /mnt/host/logs/ubuntu.log && cd dotfiles >> /mnt/host/logs/ubuntu.log && ./start.sh >> /mnt/host/logs/ubuntu.log && sleep 600' volumes: ubuntu: driver_opts: type: none # 絶対パスで指定する device: /home/user/logs o: bind
.env
環境設定用ファイル.
UBUNTU=ubuntu
run_env_test.sh
テストを実行するために使うスクリプト.
コンテナの完全初期化と実行というシンプルな内容.
#!/bin/sh # installation test for each Linux distributions TOPDIR="dists" COMMON="docker-compose.yml" readonly DEBUG=false if "${DEBUG}"; then DISTS="ubuntu" # 1つずつの環境で実行したいとき else DISTS=`ls ${TOPDIR}` fi # Run your installation process function run () { for dist in ${DISTS} do # sudo docker ps # sudo docker exec -it <hash> bash sudo docker-compose -f ${COMMON} -f ${TOPDIR}/${dist}/personal-settings.yml -p ${dist} up -d done; } # Initialize all docker containers function initialize () { sudo docker stop $(sudo docker ps -aq) sudo docker rm $(sudo docker ps -aq) sudo docker network prune -f sudo docker rmi -f $(sudo docker images --filter dangling=true -qa) sudo docker volume rm $(sudo docker volume ls --filter dangling=true -q) sudo docker rmi -f $(sudo docker images -qa) } # Show help function show_help () { echo "Usage: ./run_env_test.sh [options]" echo "[options]: " echo " run : run your installation process" echo " init: initialize all container" } # Main routines if [ "$1" == "run" ]; then run elif [ "$1" == "init" ]; then initialize else show_help fi
実行
$ ./run_env_test.sh run
これで各コンテナが実行される.
$ tail -f logs/ubuntu.log
とかすればコンテナ内で何が行われているか把握できる.
初期化
コンテナをまっさらな状態にしたいときは
$ ./run_env_test.sh init
評価
やりたかったこと(再掲)
- 作ったプログラムが各ディストリ・バージョン上で動くかどうか簡単に確かめたい
→ 各ディストリ・バージョン環境の簡単な追加,コマンド実行ができるようになった! - 開発環境で動くようになったがまっさらな環境で動くのか確認したい
→ コンテナを初期化することで毎回まっさらな環境で試せるようになった! - 上記を一斉に行いたい
→ 一斉に起動可能!
最初に設定した要件は満たした.
改善が必要なところ
- dockerは基本的にrootのため,user環境での実験作成
→add user
からやるのであれば問題ないか? - プログラムを修正してからの再度実行に手間がかかる(Dockerの初期化・再起動させる必要がある.デバッグ時の細かい試し実行がしにくい)
→ 対策考え中
所感
しばらくはこれで運用しようと思う.
Kubernetes
は勉強中のためこっちのほうが良さそうならまた同じような環境を作ろうと思う.
もっと冴えたやり方を知っている方は是非教えてください.
参考
自動テストの実行環境をDockerでお気軽引っ越し - ZOZO Technologies TECH BLOG
Docker入門(第六回)〜Docker Compose〜 | さくらのナレッジ
複数のDockerコンテナを自動で立ち上げる構成管理ツール「Docker Compose」(Dockerの最新機能を使ってみよう:第7回) | さくらのナレッジ
Dockerfileでうまくマウントできないのでdocker-compose.yml使ってマウントする(o*。_。)o - Qiita
Docker Composeのトップレベルvolumesでホストのディレクトリをマウントする - ニューなんとなく書くブログ