xin9le.net

Microsoft の製品/技術が大好きな Microsoft MVP な管理人の技術ブログです。

paiza.IOの実行環境を覗き見る方法4選

ブラウザだけで様々な言語のコードを即時実行できる、超便利なオンラインプログラミング環境 paiza.IO前の記事で C# で実行環境を調査するという遊びをやっていました。

今回はその延長として、paiza.IO の実行環境を覗いて遊ぶコードを 4 つ紹介します。「エンジニアたるもの、コードが動くならその中を覗き込まないワケには行かない!」という謎発想で作ったのが以下のサンプルコードです。Main 関数にある 4 つのメソッドはそれぞれ独立して動きますので、必要に応じてコメントを設定/解除してご利用ください

1. ファイルシステム内を探検

paiza.IO のソースコードは Docker 上の Ubuntu 環境で実行されます。私は Docker に詳しくありませんが、どうやら Docker は Hyper-V などの仮想マシンと違ってホスト OS を共有するようです。ということはファイルシステムも共通と思われ、もしファイルシステムを覗くことができればそれはホスト OS のもの!というウハウハ気分ががが...(ジュルリ (※この認識は間違ってるかもしれない)

ファイルシステムは以下のようなコードで覗くことができます。メソッドの引数にフォルダパスを与えればその階層にあるファイルとフォルダを出力画面に表示してくれます。ファイルの場合は先頭に 'f' の文字を、フォルダには 'd' の文字を付けているのでそれなりに見やすいと思います。

static void EnumerateFileSystemEntries(string path)
{
    var infos = new DirectoryInfo(path)
              .EnumerateFileSystemInfos("*.*", SearchOption.TopDirectoryOnly)
              .Select(x => new
              {
                  IsDirectory = x.Attributes.HasFlag(FileAttributes.Directory),
                  FullName = x.FullName,
              })
              .OrderByDescending(x => x.IsDirectory)
              .ThenBy(x => x.FullName);
    foreach (var x in infos)
        Console.WriteLine("{0} : {1}", x.IsDirectory ? 'd' : 'f', x.FullName);
}

/*
EnumerateFileSystemEntries("/etc/mono");

d : /etc/mono/2.0
d : /etc/mono/4.0
d : /etc/mono/4.5
f : /etc/mono/browscap.ini
f : /etc/mono/config
*/

2. 実行ファイルを叩く

先のファイル/フォルダ検索で見つかった実行ファイルをその場で動かせたら面白いでしょう。以下がそのサンプルです。メソッドに実行ファイルの絶対パスと、起動引数を与えてあげれば OK です。paiza.IO 上のコードはユーザー権限下で実行されるように制御されているため、ホスト OS にダメージを与えるようなものの実行はできません。残念...(何

static void RunExe(string exe, string argument = null)
{
    var psi = string.IsNullOrWhiteSpace(argument)
            ? new ProcessStartInfo(exe)
            : new ProcessStartInfo(exe, argument);
    psi.UseShellExecute = false;
    psi.RedirectStandardOutput = true;
    var process = Process.Start(psi);
    Console.WriteLine(process.StandardOutput.ReadToEnd());
    process.WaitForExit();
}

/*
RunExe("/usr/bin/git", "--help");

usage: git [--version] [--help] [-C <path>] [-c name=value]
           [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
           [-p|--paginate|--no-pager] [--no-replace-objects] [--bare]
           [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
           <command> [<args>]
<以下略>
*/

3. bash コマンドを叩く

Unix 系を使っていたら、やっぱり bash。先の方法で実行ファイルを直接叩く方法もありますが、bash 上で実行するコマンドは基本的にパスが通っている状態なのでコマンドの実態がどこにあるかは通常あまり気にしません。そこで、bash 上で実行しているような感覚でコマンドを叩く機能も用意しました。以下がそのサンプルです。

static void RunCommand(string command)
{
    var exe = "/bin/bash";
    var args = string.Format("-c \"{0}\"", command);
    RunExe(exe, args);
}  

/*
RunCommand("env");

HOSTNAME=f20fd8104ba6
_WAPI_PROCESS_HANDLE_OFFSET=2
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PWD=/workspace
LANG=en_US.UTF-8
RUNNER_USER=runner2
SHLVL=1
HOME=/workspace
_=/usr/bin/env
*/

ぶっちゃけ bash を起動引数付きで呼び出しているだけですが、「コマンド入力 → Ctrl + Enter」という心地よい良いコンボが使えます。ただし、「カレントディレクトリを変更してからファイル確認」のような複合的なコマンドには向きません。

4. ファイルを開く

ファイルシステムを探索して見つけたファイルは中身を確認したいですよね。その機能が以下です。ファイルパスを与えるだけなので簡単ですね。

static void ReadFile(string path)
{
    var text = File.ReadAllText(path);
    Console.WriteLine(text);
}

/*
ReadFile("/workspace/build_time.txt");

    Command being timed: "timeout 15 dmcs -sdk:4.5 Main.cs"
    User time (seconds): 0.54
    System time (seconds): 0.02
    Percent of CPU this job got: 99%
    Elapsed (wall clock) time (h:mm:ss or m:ss): 0:00.56
    Average shared text size (kbytes): 0
    Average unshared data size (kbytes): 0
    Average stack size (kbytes): 0
    Average total size (kbytes): 0
    Maximum resident set size (kbytes): 41820
    Average resident set size (kbytes): 0
    Major (requiring I/O) page faults: 0
    Minor (reclaiming a frame) page faults: 9290
    Voluntary context switches: 20
    Involuntary context switches: 50
    Swaps: 0
    File system inputs: 0
    File system outputs: 16
    Socket messages sent: 0
    Socket messages received: 0
    Signals delivered: 0
    Page size (bytes): 4096
    Exit status: 0
*/

ちなみに、書き込み権限があるフォルダであればファイルを書き換えたり消したりすることもできます。とは言え、次にコードが実行されるときには Docker コンテナの力ですべてが元通りになっているので、環境破壊を頑張っても無力感しか感じませんが...。

おまけ - GHOST 対策のチェック

2015 年 1 月末に Linux 界隈を賑わせた GHOST。GNU C Library (glibc) というライブラリに存在したバッファオーバーフローの脆弱性(CVE-2015-0235)です。せっかくコマンドを実行する機能を作ったので、paiza.IO の環境でこの対策がなされているか調べてみました。glibc のバージョン確認方法はこちらのサイトを参考にしました。

RunCommand("/lib/x86_64-linux-gnu/libc.so.6");

/*
GNU C Library (Ubuntu EGLIBC 2.19-0ubuntu6.5) stable release version 2.19, by Roland McGrath et al.
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 4.8.2.
Compiled on a Linux 3.13.11 system on 2014-12-04.
Available extensions:
   crypt add-on version 2.1 by Michael Glad and others
   GNU Libidn by Simon Josefsson
   Native POSIX Threads Library by Ulrich Drepper et al
   BIND-8.2.3-T5B
libc ABIs: UNIQUE IFUNC
For bug reporting instructions, please see:
<https://bugs.launchpad.net/ubuntu/+source/eglibc/+bugs>.
*/

Ubuntu 環境で version 2.19 が適用されていることが分かります。ちゃんと修正されたモジュールが使われている感じですね。よかった。

paiza の裏側解説

2015 年 2 月 19 日 ~ 20 日にかけて目黒雅叙園で開催された Developers Summit 2015。ここで paiza の裏側でガシガシ利用されている Docker 周りの詳説がされたようです。なかなか興味深いですね。