えぬえす工房
  • Search results
avatar えぬえす工房
2016/10/13

Bash on Ubuntu on Windowsのファイルシステムってどうなってるの?

Windows 10 Anniversary UpdateではWindows上でLinuxバイナリ版のBash等を動かす機能である、Bash on Windows on Ubuntuが追加されました。

Anniversary Updateの提供開始直後に「やっと来た!」とわくわくしながらアップデートして試した方も多いはずです。

この記事では、今更ながらBash on Windows on Ubuntuのファイルシステムがどのように作られているかを解説します。

Linuxバイナリを動かすためのサブシステム

「ファイルシステム関係ないやん!」って思った方もいると思いますが、簡単に説明する必要があると思うので説明します。

普段意識していないと思われますが、WindowsのNTカーネルはWin32APIを使ったアプリケーション以外を動かすための仕組みが存在します。 それがサブシステムと言われる仕組みで、かつてはOS/2やPOSIX準拠のアプリケーションを動かすために使用されました。

このサブシステムの仕組みを使って実装されたものにWindows Subsystem for Linux(WSL)があり、WSLを使用してBash on Ubuntu on Windowsは実現されています。 要するに、Bash on Ubuntu on Windowsの内部を知りたいのであればWSLを調べる必要があります。

詳しくは以下のページを参照すると良いでしょう。

Windows Subsystem for Linux Overview

Windows Subsystem for Linuxの中身を詳しく見る

Windows Subsystem for Linux Internals

WSLのファイルシステム

WSLのファイルシステムについては、MSDN公式のブログでは“WSL File System Support”という記事で触れられています。

以下の画像は、この記事から引用したWSLのファイルシステムの図です。

WSLのファイルシステム

WSL上で動作するbash等のアプリケーションがopen(2)などのシステムコールを呼び出すと、その呼び出しはLxcore.sysで処理されます。 Lxcore.sys内部ではパスに応じて適切なファイルシステムプラグインへと振り分け、システムコールを処理します。

ファイルシステムプラグインは、大きく分けて4つ存在します。

  • VolFs

    /, /root, /homeなど、WSLで使用されるファイルの大半を格納している

  • DrvFs

    /mnt/cをはじめとした、Windowsで認識されているドライブへのアクセスを提供

  • TmpFs

    /devなど、一時的に利用されるファイルが格納されるインメモリファイルシステム

  • ProcFs, SysFsなど

    Linuxの/procや/sysなどに相当する機能を提供

上記の通り、WSLで使用されるファイルは通常はVolFsに格納されます。

VolFsの正体

VolFsの正体は、%USERPROFILE%\AppData\Local\lxss以下のフォルダです。

通常このフォルダはエクスプローラ等からは見えなくなっていますが、ファイル名を指定して実行等で直接アクセスすると見ることができます。 「WindowsからもVolFsの中身を読み書きできるのでは?」と思う人も居るかもしれませんが、これは半分合っていて半分間違っています。

実はVolFsはWSL上で設定したパーミッションや所有者などのLinux特有の情報が保持しており、WSLを通さずにWindowsから直接VolFsの中身を書き換えるとWSLからファイルが見えなくなったりします (“Filesystem problems: Consolidated”を参照)

VolFsはどのようにLinux特有の情報を保持している?

ここまでで述べたとおり、VolFsはパーミッションや所有者などのLinux特有の情報を保持しているため、WSL以外で書き換えるとトラブルの元になります。 しかし、一体なぜそのようなことになるのでしょうか?

答えは、VolFsはLinux特有の情報を保持するためにNTFSの拡張属性を使用しているためです。 WSL以外で書き換えるとファイルに付加された拡張属性が消失してしまうため、ファイルが見えなくなってしまうのです。

NTFSの拡張属性は、追加のメタデータ等を保持するためによく使われる「代替データストリーム」とは異なるものです。 そのためdirコマンドで拡張属性の存在を確認することはできず、専用のツールが必要です。 WSLで使用されている拡張属性を見るためのツールとして、GitHub上にlxsstatと呼ばれるツールが公開されています。 このツールはLinuxのstat(1)コマンドと似たもので、引数にVolFsに含まれているファイルを渡すとそのファイルに含まれている拡張属性を読みだして表示してくれます。 例として、/etc/shadowの情報を見たときの出力は以下の通りです。

lxsstat C:\Users\NV\AppData\Local\lxss\rootfs\etc\shadow
  File: 'C:\Users\NV\AppData\Local\lxss\rootfs\etc\shadow'
  Size: 906             Blocks: 8          IO Block: 4096   regular file
Device: 0h/0d   Inode: 39406496739841858  Links: 1
Access: (0640/-rw-r-----)  Uid: (    0/--------)   Gid: (   42/--------)
Access: 2016-10-12 10:38:09.468924800 +0000
Modify: 2016-10-12 10:38:09.468924800 +0000
Change: 2016-10-12 10:38:09.474939300 +0000

NTFSの拡張属性について

NTFSの拡張属性を理解するためには、まず最初にNTFS上でどのようにしてファイルが格納されているかを知る必要があります。

NTFSにおいて、ファイルはファイルレコードとして格納されます。 これはUNIXにおけるiノードのようなもので、以下に示すヘッダが先頭に付加されています。

オフセット サイズ OS 説明
0x00 4   マジックナンバー ‘FILE’
0x04 2   アップデートシーケンスへのオフセット
0x06 2   アップデートシーケンスのサイズS(WORD単位)
0x08 8   $LogFileのシーケンス番号(LSN)
0x10 2   シーケンス番号
0x12 2   ハードリンクのカウント
0x14 2   最初の属性へのオフセット
0x16 2   フラグ
0x18 4   ファイルレコードの実際のサイズ
0x1C 4   ファイルレコードの確保済みサイズ
0x20 8   元となるファイルレコードへの参照
0x28 2   次の属性ID
0x2A 2 XP以降 4バイト境界へ合わせるためのパディング
0x2C 4 XP以降 MFTレコードにおける番号
  2   アップデートシーケンス番号
  2S-2   アップデートシーケンス配列

このヘッダ以降には、ファイルについての各種属性が続いており、最後にエンドマーカとして0xFFFFFFFFが出現します。 つまり、ファイルレコードは以下のような構成になっています。

ヘッダ
属性1
属性2
...
エンドマーカ(0xFFFFFFFF)

属性は様々な種類があり、代表的なものとして以下のものがあります。

属性タイプ 名前 説明
0x10 $STANDARD_INFORMATION ファイルの作成日時など
0x30 $FILE_NAME ファイル名
0x50 $SECURITY_DESCRIPTOR セキュリティ記述子
0x80 $DATA データ(データも属性!)
0xE0 $EA 拡張属性

実際に拡張属性を直接見てみる

Windows上で動作するGUIツールもありますが、ここではNTFS-3Gに含まれているntfscatコマンドを使用してWSL上のファイルに含まれている拡張属性を見てみます。 例として、/etc/shadowの拡張属性を見てみます。引数に-a [属性の名前($は除く)]を与えることで、特定の属性のみを見ることができます。

$ sudo ntfscat -a EA /dev/sda3 /Users/NV/AppData/Local/lxss/rootfs/etc/shadow | hexdump -C
00000000  48 00 00 00 00 07 38 00  4c 58 41 54 54 52 42 00  |H.....8.LXATTRB.|
00000010  00 00 01 00 a0 81 00 00  00 00 00 00 2a 00 00 00  |............*...|
00000020  00 00 00 00 80 39 f3 1b  80 39 f3 1b a4 ff 4e 1c  |.....9...9....N.|
00000030  91 12 fe 57 00 00 00 00  91 12 fe 57 00 00 00 00  |...W.......W....|
00000040  91 12 fe 57 00 00 00 00                           |...W....|
00000048

このままではよく分からないですが、lxsstatのヘッダファイルLxss::LXATTRB構造体として各フィールドの意味が記載されています。 以下に、構造体の前に付加されているのフィールドを含めた一覧表を書いておきます。なお、この表はWSL上のファイルに付加される拡張属性であるLXATTRBを想定しています。

オフセット サイズ フィールド名 説明
0x00 4   次の拡張属性へのオフセット
0x04 1   フラグ
0x05 1   拡張属性名の長さ(N=7)
0x06 2   拡張属性のデータの長さ(V=0x38)
0x08 7   拡張属性の名前(LXATTRB)
0x0F 1   パディング
0x10 4 unknown1 不明
0x14 4 st_mode パーミッション
0x18 4 st_uid UID
0x1C 4 st_gid GID
0x20 4 st_rdev デバイスファイルのメジャー/マイナー番号
0x24 4 atime_extra 最終アクセス時刻(小数点以下の部分)
0x28 4 mtime_extra 最終変更時刻(小数点以下の部分)
0x2C 4 ctime_extra 最終ステータス変更時刻(小数点以下の部分)
0x30 8 atime 最終アクセス時刻
0x38 8 mtime 最終変更時刻
0x40 8 ctime 最終ステータス変更時刻

この表には無い、iノード番号等は別の情報から決定されるようです。

おわりに

Bash on Ubuntu on Windowsこと、Windows Subsystem for Linuxのファイルシステムについて解説しました。 久しぶりにこの手の記事書いたので変なところがあったらコメント等にお願いします。

ずっと放置してる壊れたNTFSパーティションどうしようかなぁ…

Tagged with:

Author

avatar
NV

気づいたら組み込みセキュリティをやっているエンジニア

Comments