雑記」カテゴリーアーカイブ

Jumbo Frame + WinServer 2019 の Hyper-V の通信は妙に遅い。

どうもみむらです。
最近梅雨に入ったのか夏に入ったのか、色々パッとしなくて困る日々が続いております。

先日 Windows Server 2019 (もしくは Hyper-V Server 2019) を Jumbo Frame 環境下で用いると、通信が遅くなるということが分かりましたのでメモがてら。
同じような問題に遭遇した方の一助のとなればありがたいです。


事象:

Windows Server 2019 上の Hyper-V に展開された VM において
RSC (Receive Side Coalescing, Linux では Large Receive Offload で知られている) が有効
かつ VM が Jumbo Frame の環境において、VMから見て受信方向の通信が遅くなる。

注1:Windows Server 2019 では RSC は既定で有効になっています。
注2:MTU が 2850 bytes を上回る ( >2850 ) 場合に速度低下が発生するようです。

原因:

VMSwitch において Reassemble されたパケットが VMに届かなくなる。

回避方法:

RSC を無効にするか MTU を 1500 にする

方法1:ゲスト VM の MTU を 1500 にする。
 例) ip l set mtu 1500 dev eth0

方法2:ホスト側でRSC 機能を VMSwitch 単位で無効にする
 例)Set-VMSwitch (Switch名) -EnableSoftwareRsc $false

方法3:ゲストVM において ethtool 等を用いて “large-receive-offload” を off にする
 Linux) ethtool -K eth0 large-receive-offload off
 Windows) Set-NetAdapterAdvancedProperty “*” -DisplayName “Recv Segment Coalescing” -RegistryValue 0


パケットの気持ちになってみる

最近パケットの気持ちになる、が一部界隈で有名ですので「なってみよう」と思います。

ざっくりと構成は下記の通り。
Hyper-V サーバ内に立てられた VM-01 との通信について Hyper-V サーバの前段のミラー (CAP-01) と VMSwitch のミラー (CAP-02) で通信をキャプチャして挙動を確認しよう、という構成になっています。

なお、各キャプチャは同タイミングでのものではありません。ご了承ください


VM-01 での curl での速度比較

事象を確認するためにまずは curl で適当な通信を発生させて速度を見てみます。

MTU 1500 の場合は Average Speed が 10.7M となっているのに対し
MTU 9000 の場合は Average Speed が 44024 となっています。


MTU 9000 の時の VM-01 のキャプチャ

上記のようになります。

No. 12 において Seq=141, Ack=1409 をサーバに対して返答していますが
その次にやってきたパケット (No.13) は Seq=16897 になっています。
(通常は直前の Ack と同じ番号の Seq が返ってきます)

そのため No.14 において Ack=1409 を再度送信され、
サーバ側からは No.16 において Seq=1409 の返答(再送)が起きてしまっています。


MTU9000 の時の CAP-02 のキャプチャ

上記のようになります。
No. 13 までは先ほどの VM-01 と同じような流れになっています。

ですが No.14 付近から 1474 bytes ではないパケットが流れはじめます。
またそれを境にして、 Ack と Seq の関係が壊れはじめるのも確認が出来ます。

たとえば No.14 は Seq=1409 として 2882 bytes の通信が行われていますが
その後の ACK (No.16) では Ack=1409 として返答が行われていることが分かり、
通信が正常に行われていないことがここから読み取れます。

またこの 1475 bytes でないパケットについては先述の VM-01 のキャプチャにおいては確認出来ず、その後発生した No.25 の Ack に対する No.26 の 1474 bytes の通信が行われて初めて VM-01 側にパケットが到達しているように見受けられます。


MTU9000 の時の CAP-01 のキャプチャ

上記のようになります。

インターネットを介して No.10 ~ No.19 に掛けて勢いよく通信が行われていますが
その後 No.20 において Ack=1 が返され、No.23 において再送が行われています。

この間 Seq は 12673 まで増えており、この No.19 に当該するパケットは先述の CAP-02 においても No.21 として観測できているように見受けられます。


以上の事象、また CAP-02 においてパケットをとり続けると、 reassemble されたパケットが送信された後に再送要求が発生していること、そして RSC を Disable にするとこれらの事象が解決することから RSC (LRO) が原因と判断しました。

また繰り返しではありますが、
Windows Server 2022 (Preview) や Windows 10 (21H1) では発生しないことを確認していますので Windows Server 2019 特有の問題 ( 1809 ベースの Hyper-V 特有? ) と判断しています。


番外:ドライバベースで治せないかやってみる

Linux のドライバは自由に修正したりして実験できますので、
これで何か出来ないかやってみます。

1.そもそも機能を切る:

netvsc の 603 行目に下記のような記述があります
https://github.com/torvalds/linux/blob/master/drivers/net/hyperv/netvsc.c#L603

/* Negotiate NVSP protocol version */
static int negotiate_nvsp_ver(struct hv_device *device,
			      struct netvsc_device *net_device,
			      struct nvsp_message *init_packet,
			      u32 nvsp_ver)
{

/// 省略 ///

	if (nvsp_ver >= NVSP_PROTOCOL_VERSION_61)
		init_packet->msg.v2_msg.send_ndis_config.capability.rsc = 1;

/// 省略 ///

	return ret;
}

この nvsp_ver で Windows Server 2019 とそれ以降の区別を試みましたが
共に NVSP_PROTOCOL_VERSION_61 (0x60001) が返るため、区別は出来ませんでした。

もちろんですが、”init_packet->msg.v2_msg.send_ndis_config.capability.rsc = 0;” とすると RSC の機能が恒久的に無効になります。


2.別パラメータから値を推測する等で値を修正する:

こちらですが、そもそも VMBus 経由での VMQ の割込が来ないため
修正は難しいという形になりました。

Hyper-V のネットワーク通信は下記のようなアーキテクチャになっています。

vmq コンポーネント

引用元 : https://docs.microsoft.com/ja-jp/windows-hardware/drivers/network/vmq-components

親(ホスト)が持つ NetVSP (VMSwitch) に対して VMBus 経由で接続するアーキテクチャになっており、Linux の netvsc ドライバにおいても 1665 行目付近でその接続が行われていることが伺えます。

struct netvsc_device *netvsc_device_add(struct hv_device *device,
				const struct netvsc_device_info *device_info)
{

/// 省略 ///

	/* Enable NAPI handler before init callbacks */
	netif_napi_add(ndev, &net_device->chan_table[0].napi,
		       netvsc_poll, NAPI_POLL_WEIGHT);

	/* Open the channel */
	device->channel->rqstor_size = netvsc_rqstor_size(netvsc_ring_bytes);
	ret = vmbus_open(device->channel, netvsc_ring_bytes,
			 netvsc_ring_bytes,  NULL, 0,
			 netvsc_channel_cb, net_device->chan_table);

	if (ret != 0) {
		netdev_err(ndev, "unable to open channel: %d\n", ret);
		goto cleanup;
	}

	/* Channel is opened */
	netdev_dbg(ndev, "hv_netvsc channel opened successfully\n");

	napi_enable(&net_device->chan_table[0].napi);

	/* Connect with the NetVsp */
	ret = netvsc_connect_vsp(device, net_device, device_info);
	if (ret != 0) {
		netdev_err(ndev,
			"unable to connect to NetVSP - %d\n", ret);
		goto close;
	}

/// 省略 ///

}

https://github.com/torvalds/linux/blob/9d31d2338950293ec19d9b095fbaa9030899dcb4/drivers/net/hyperv/netvsc.c#L1648


試しに netvsc_receive 関数を下記のように編集してみると下記のような出力が得られました。

static int netvsc_receive(struct net_device *ndev,
			  struct netvsc_device *net_device,
			  struct netvsc_channel *nvchan,
			  const struct vmpacket_descriptor *desc)
{
	struct net_device_context *net_device_ctx = netdev_priv(ndev);
	struct vmbus_channel *channel = nvchan->channel;
	const struct vmtransfer_page_packet_header *vmxferpage_packet
		= container_of(desc, const struct vmtransfer_page_packet_header, d);
	const struct nvsp_message *nvsp = hv_pkt_data(desc);
	u32 msglen = hv_pkt_datalen(desc);
	u16 q_idx = channel->offermsg.offer.sub_channel_index;
	char *recv_buf = net_device->recv_buf;
	u32 status = NVSP_STAT_SUCCESS;
	int i;
	int count = 0;

  // 下記行を追記
	netif_info(netdevice_ctx, rx_err, ndev, "BUF-SIZE : %u, SEC-SIZE : %u, RSC-PKTLEN %u\n",
    net_device->recv_buf_size, net_device->recv_section_size,
    nvchan->rsc.pktlen);

	/* Ensure packet is big enough to read header fields */
	if (msglen < sizeof(struct nvsp_message_header)) {
		netif_err(net_device_ctx, rx_err, ndev,
			  "invalid nvsp header, length too small: %u\n",
			  msglen);
		return 0;
	}

/// 以下省略 ///

BUF-SIZE が Window Size にちょっと足したもの、SEC-SIZE が MTU にちょっと足したものの値になり、 RSC-PKTLEN がフレームサイズと同じ値を指し示すようです。

RSC-PKTLEN の値は 1474 を示しており、冒頭のパケットキャプチャと同じような感じになっていることが読み取れます。

なお、同じ VM を同じ設定で Windows 10 の上に構築した場合は下記のようになります。

RSC-PKTLEN が十分に大きな値になっており、 RSC にて reassemble されたパケットが受信出来ていることが分かります。

ドライバを追いかけてみたのですが、正しい値が別パラメータに入っていることなどはなく、また NetVSP の割込が来ないため修正は難しいと考えられました。


まとめ

Windows Server 2019 の Hyper-V を使用して VM を作成する場合は

・MTU を 1500 以下に設定して RSO が正常に機能するようにして使う
・Jumbo Frame を有効にしたい場合は RSO を Disable にする

のどちらかで利用しないと、落とし穴があるという話です。

執筆時点の最新版である “10.0.17763.1999” でもこの事象は発生していますので、
お気をつけくださいませ。


P.S.

割と海外のフォーラムだと “RSO を無効にしたら良くなった!” 的なのはちらほら報告されているみたいですね。。修正されたらいいなとぼんやり思ってます。。

https://social.technet.microsoft.com/Forums/en-US/8aa6a88c-ffc8-4ede-abfc-42e746ff5996/windows-server-2019-hyperv-guest-on-windows-server-2019-hyperv-host?forum=winserverhyperv

https://www.doitfixit.com/blog/2020/01/15/slow-network-speed-with-hyper-v-virtual-machines-on-windows-server-server-2019/

ASUSTOR DataSync Center の HDD アクセスを静かにする

どうもみむらです。

時代の流れに沿って(?)最近 NAS を買ってみました。

録画データを置いておいたり、時々使う ISO ファイルの保存場所に使ってみたり DLNA で色んな機器から触れるようになって QoL があがったりと「NAS は良いものだ」なんて感じている今日この頃です。

DataSync Center というソフトを入れると、外部のクラウドサービスとも同期してくれるので、スマホで撮った画像をクラウド経由で NAS に入れて DLNA で触れるようになったり・・等々、とても QoL が上がっております。

ただそんな NAS と DataSync Center なのですが、このソフトが常時 HDD にアクセスに行くために夜中も音がしてしまい、これは精神衛生上よろしくないので静音化を図ることにしてみました。


内容について

紹介する内容を実施した後で不具合が発生しても、私自身は責任を負えないため自己責任でお願い致します。

下記の内容としては稼働中の datacenter のメタデータを tmpfs に配置して解決というものになります。後半に作成物がありますので、お急ぎの方はそちらを参照ください。


1. 原因を追う

1.1. entware のインストールと SSH アクセスの許可

DataSync Center が何にアクセスしているかをトレースするため
entware のインストールと、SSH アクセスの許可を行います。

App Central から entware-ng インストール

以上の手順を踏むと “/opt/bin/opkg” から entware のパッケージマネージャが使えるようになります。


1.2. strace で挙動を見る

sudo su 等で root に登った後、下記のコマンドでインストールします。

なお opkg のパッケージは基本的に /opt 以下で触れるように配置されます。
またそれらのデータ群は /usr/local/AppCentral/entware-ng/opt/ 以下にシンボリックリンクが張られるようになるため、App Central から entware を削除すると、同時に opkg で入れたファイル群も削除されるようです。

# /opt/bin/opkg update
Downloading http://bin.entware.net/armv7sf-k2.6/Packages.gz
Updated list of available packages in /opt/var/opkg-lists/entware

# /opt/bin/opkg install strace
Installing strace (5.10-1) to root...
Downloading http://bin.entware.net/armv7sf-k2.6/strace_5.10-1_armv7-2.6.ipk
Configuring strace.

次に ps コマンドでプロセスを探します

# ps | grep datasync
 3442 root       0:00 grep datasync
 5145 ***** 1295:0 /usr/local/AppCentral/datasync-center/bin/datasync_center 1

上記の例では PID は 5145 になっていることが分かります。
では strace でアタッチして挙動を観察しましょう。

# /opt/bin/strace -p (PID)
(上記例の場合は /opt/bin/strace -p 5145 と入力する)

gettimeofday({tv_sec=1619546282, tv_usec=544859}, NULL) = 0
gettimeofday({tv_sec=1619546282, tv_usec=544976}, NULL) = 0
gettimeofday({tv_sec=1619546282, tv_usec=545057}, NULL) = 0
stat64("/usr/builtin/etc/ldapclient/asldapclient.conf", 0xbead4188) = -1 EACCES (Permission denied)
stat64("/etc/passwd", {st_mode=S_IFREG|0644, st_size=461, ...}) = 0
open("/etc/passwd", O_RDONLY|O_LARGEFILE) = 12
read(12, "root:x:0:0:,0,,root:/root:/bin/s"..., 461) = 461
close(12)                               = 0
stat64("/etc/passwd", {st_mode=S_IFREG|0644, st_size=461, ...}) = 0
open("/etc/passwd", O_RDONLY|O_LARGEFILE) = 12
read(12, "root:x:0:0:,0,,root:/root:/bin/s"..., 461) = 461
close(12)                               = 0
stat64("/etc/passwd", {st_mode=S_IFREG|0644, st_size=461, ...}) = 0
open("/etc/passwd", O_RDONLY|O_LARGEFILE) = 12
read(12, "root:x:0:0:,0,,root:/root:/bin/s"..., 461) = 461
close(12)                               = 0
stat64("/etc/nas.conf", {st_mode=S_IFREG|0644, st_size=3257, ...}) = 0
open("/etc/nas.conf", O_RDONLY|O_LARGEFILE) = 12
read(12, "[Basic]\nModel = AS1002T\nVersion "..., 3257) = 3257
close(12)                               = 0
lstat64("/share", {st_mode=S_IFDIR|0755, st_size=1024, ...}) = 0
lstat64("/share/home", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/share/home/*******", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
open("/share/home/*******/.datasync-center/connections/1/user_info.json.lock", O_RDWR|O_CREAT|O_LARGEFILE|O_CLOEXEC, 0777) = 12
flock(12, LOCK_EX)                      = 0
open("/share/home/*******/.datasync-center/connections/1/user_info.json_5145.tmp", O_WRONLY|O_CREAT|O_TRUNC, 0644) = 15
write(15, "{\"quota\":1131723882496,\"normal\":"..., 109) = 109
close(15)                               = 0
rename("/share/home/*******/.datasync-center/connections/1/user_info.json_5145.tmp", "/share/home/*******/.datasync-center/connections/1/user_info.json") = 0
open("/etc/passwd", O_RDONLY|O_CLOEXEC) = 15
_llseek(15, 0, [0], SEEK_CUR)           = 0
fstat64(15, {st_mode=S_IFREG|0644, st_size=461, ...}) = 0
mmap2(NULL, 461, PROT_READ, MAP_SHARED, 15, 0) = 0xb6faa000
_llseek(15, 461, [461], SEEK_SET)       = 0
munmap(0xb6faa000, 461)                 = 0
close(15)                               = 0
geteuid32()                             = 1000
chmod("/share/home/*******/.datasync-center/connections/1/user_info.json", 0700) = 0
flock(12, LOCK_UN)                      = 0
close(12)                               = 0
rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
rt_sigaction(SIGCHLD, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
nanosleep({tv_sec=1, tv_nsec=0}, 

定期的な read は良いとしても 「user_info.json_5145.tmp という名前でファイルを書いて、 user_info.json を消して、user_info.json に置換する」というのを定期的に行われるのは、やはりあんまり心臓にもよろしくないですね。。

ということで、これらを tmpfs に配置するようにしてみます。
(ディスクの書き込み先を RAM 上に作成したドライブに変更して、ディスクへの書き込みを行わせない)


2. データを tmpfs に配置するようにする。

2.1. 現在のデータをコピーして取っておく

方針としては、起動時・シャットダウン時にメタデータをコピーしておき
稼働中は tmpfs に対してずっと書かせるようにしてみます。

/share/home/(USERNAME)/.datasync-center/connections 以下のデータがずっと読み書きされていますので、connections 以下を tmpfs にするように丸っとコピーを取ります。

# /volume1/.@plugins/etc/init.d/K50datasync-center start
(DataSync Center を停止する)

# cd /share/home/(USERNAME)/.datasync-center/
( ユーザ名が hogeo であれば cd /share/home/hogeo/.datasync-center/ になる.)

# ln -ln
(UID,GID を調べておく. 下記の例では UID=1000, GID=100)
drwxrwxrwt    3 1000     100             60 Apr 23 11:55 connections

# cp -R connections original_connections
(コピーを取る)

# cd original_connections
# find -name "*lock" | xargs rm
# find -name "pid" | xargs rm
# find -name "sock" | xargs rm
(最後3つは誤作動の原因になりそうなものの削除です)

2.2. 起動時・シャットダウン時の挙動をサービスとして記述する

下記のサイトを参考にすると “/volume1/.@plugins/etc/init.d/ ” あたりにスクリプトを配置すると幸せになれそうですので、ここにスクリプトを記述していきます。

Digging in to the Linux install on a NAS
https://ljones.dev/blog/digging-into-nas/

基本的には下記2つのスクリプトを作成するだけです。


/volume1/.@plugins/etc/init.d/S01prepare-datasync-center

このスクリプトでは既存の connections ディレクトリを削除して tmpfs (かつ UID, GID はユーザに合わせる) をそこにマウントさせた後、コピーしておいたものを書き戻す処理になります。

下記スクリプトを利用される際は (USERNAME) 部分および (UID) 部分をご自身の環境に合わせて書き換えてください。

#!/bin/sh -e

rm -rf /share/home/(USERNAME)/.datasync-center/connections
mkdir /share/home/(USERNAME)/.datasync-center/connections
mount tmpfs -t tmpfs /share/home/(USERNAME)/.datasync-center/connections -o uid=(UID),gid=100

cp -R /share/home/(USERNAME)/.datasync-center/original_connections/* /share/home/(USERNAME)/.datasync-center/connections/
chown -R (UID):100 /share/home/(USERNAME)/.datasync-center/connections/

/volume1/.@plugins/etc/init.d/K99store-datasync-center

このスクリプトでは既存の connections ディレクトリの中身を待避させ、かつ誤作動の原因になりそうなものを削除する内容になります。

上記同様、下記スクリプトを利用される際は (USERNAME) 部分および (UID) 部分をご自身の環境に合わせて書き換えてください。

#!/bin/sh -e

cp -R /share/home/(USERNAME)/.datasync-center/connections/* /share/home/(USERNAME)/.datasync-center/original_connections/
find /share/home/(USERNAME)/.datasync-center/original_connections/ -name "*lock" | xargs rm
find /share/home/(USERNAME)/.datasync-center/original_connections/ -name "pid" | xargs rm
find /share/home/(USERNAME)/.datasync-center/original_connections/ -name "sock" | xargs rm

上記二つのスクリプトを書き上げた後は chmod で実行権限を与えることを忘れずに。

# vi /volume1/.@plugins/etc/init.d/S01prepare-datasync-center
( スクリプトを書く。書き上がったら :wq で抜ける )
# vi /volume1/.@plugins/etc/init.d/K99store-datasync-center
( スクリプトを書く。書き上がったら :wq で抜ける )
# chmod 755 /volume1/.@plugins/etc/init.d/S01prepare-datasync-center
# chmod 755 /volume1/.@plugins/etc/init.d/K99store-datasync-center

2.3. NAS を再起動して動作することを確認する

再起動後に root で mount コマンドを実行して tmpfs がマウントされていることを確認します。

# mount
rootfs on / type rootfs (rw)
proc on /proc type proc (rw,relatime)
sysfs on /sys type sysfs (rw,relatime)
devpts on /dev/pts type devpts (rw,relatime,gid=5,mode=620,ptmxmode=000)
tmpfs on /tmp type tmpfs (rw,relatime)
/dev/md0 on /volume0 type ext4 (rw,relatime,data=ordered)
/dev/loop0 on /share type ext4 (rw,relatime)
/dev/md1 on /volume1 type ext4 (rw,relatime,data=ordered,jqfmt=vfsv1,usrjquota=aquota.user,grpjquota=aquota.group)
/dev/md1 on /share/home type ext4 (rw,relatime,data=ordered,jqfmt=vfsv1,usrjquota=aquota.user,grpjquota=aquota.group)
/dev/md1 on /share/Web type ext4 (rw,relatime,data=ordered,jqfmt=vfsv1,usrjquota=aquota.user,grpjquota=aquota.group)
/dev/md1 on /share/Music type ext4 (rw,relatime,data=ordered,jqfmt=vfsv1,usrjquota=aquota.user,grpjquota=aquota.group)
/dev/md1 on /share/Photos type ext4 (rw,relatime,data=ordered,jqfmt=vfsv1,usrjquota=aquota.user,grpjquota=aquota.group)
/dev/md1 on /share/Pictures type ext4 (rw,relatime,data=ordered,jqfmt=vfsv1,usrjquota=aquota.user,grpjquota=aquota.group)
/dev/md1 on /share/Movie type ext4 (rw,relatime,data=ordered,jqfmt=vfsv1,usrjquota=aquota.user,grpjquota=aquota.group)
/dev/md1 on /share/MSDN type ext4 (rw,relatime,data=ordered,jqfmt=vfsv1,usrjquota=aquota.user,grpjquota=aquota.group)
/dev/md1 on /share/Media type ext4 (rw,relatime,data=ordered,jqfmt=vfsv1,usrjquota=aquota.user,grpjquota=aquota.group)
tmpfs on /share/home/******/.datasync-center/connections type tmpfs (rw,relatime,uid=****,gid=100)

また DataSync Center を開き、正常に動作していることを併せて確認します。


3. 後片付け

App Central から entware-ng の削除 (インストールした場合) と、

SSH の無効化を忘れずに行っておきましょう。


ということで・・。

ここまで完了すると、HDD への DataSync Center による常時アクセスが無くなるため、 “Disk Hibernation” の設定に従って、未使用時間は眠ってくれるようになります。

快適な睡眠も得られて、めでたしめでたし・・です!
(書いてる時間的に寝られているのか、というのは一端脇に置いておきましょう)

素敵な NAS ライフをー!

Writeup : 2020年に修正されたとある脆弱性について

どうもみむらです。

年末です。後数時間で年が変わります。
私はといえば、「千葉県少年少女オーケストラとアキラさんの大発見コンサート2020」の放送を見ながら、この記事を書き終えたらソバをと最後の力をふりしぼっているところです。

宮川彬良さんのアルバム、時々仕事中にも聞いておりますが
音楽の中に遊びというか楽しみポイントが多数含まれているのもあって、すごい人だなと思うわけです。

・・・いや、今回はそういうことを書きたいわけではなくて。


今年、9月頃にとある一つの脆脆弱性が修正されました。

私が報告者になっているもので古いバージョンの製品に含まれていたものではありますが、
色々なベンダーさんでもやってしまいそうな知見が多々ありましたので
軽くご紹介できたらな、と。

なお、仕事柄脆弱性診断は行っておりますが、
趣味の範囲での発見で既に修正されているものになります。

またこの記事は特定の製品やブランドを貶めることは一切目的としておりません
自社の製品に対して「こういう脆弱性あるかも知れない」という気づきとして使って頂くことを目的としておりますので、ご理解ください。


今回の脆弱性では
「ソフトウェアアップデート」の処理において下記のような問題点がありました。

  • ソフトウェアアップデートのファイルの検証に不備があり
    任意のファイルを挿入できた。
  • ソフトウェアアップデートのファイルの処理に不備があり
    高権限で任意のコードを実行できた。

1.ソフトウェアアップデートのファイル検証

今回のソフトウェアではアップデートの方式が下記のような仕組みになっていました:

  1. サーバからアップデートのメタ情報を取得する
  2. メタ情報に手元のソフトウェアより新しいソフトウェアがあればダウンロードする
  3. アップデートに際し追加の処理が必要な場合は実行する

今回一つ目の問題点だったのは
「ソフトウェアアップデートのファイル検証」が不十分だったことでした。

ダウンロードしたファイルに付属しているメタデータは下記のような構造でした。
(実際にはもっと多くの情報などがあり、このように簡易な形にはなっていません)

ここにおいてのファイルチェックの方法に下記の不備がありました。
(繰り返しではありますが既に修正済みです)

  • “file” 要素で記述されているファイルのみチェックされる
    記述されていないファイルはチェックされない
  • “file” 要素で書かれている内容が MD5 や SHA1によるファイルのハッシュのみだった。
    → ファイルの整合性は検証できるが、
     「信頼出来るところからのものか」を検証できない

今回のケースでは、その先の処理でファイルの信頼性の検証は行われていましたが
ソフトウェアアップデートの処理を作成する際に上記のような項目のテストは
抜けがちになるのではないかと感じています。

対策としては下記のような追加の検証処理の追加や、
テストコードの追加が出来るとよいかと思います。

  1. MD5 や SHA1 などの整合性チェックに加えて
    電子署名を用いたアップデートファイルの信頼性の検証を付加する
  2. アップデートファイルが zip や tar といった一般的なアーカイブファイルを併用している場合などは、メタ情報などに記述されていないファイルが入っていた場合に検証を失敗させるなどする。

1に関しては例えば OpenSSL を用いて下記のような処理で実現できます:

# ベンダーが持つ秘密鍵で電子署名を作成する
$ openssl dgst -sha256 -sign private-key.pem patch.zip > patch.sig

# 受け取り側では公開鍵と提供された署名 (patch.sig) を用いて検証する
$ openssl dgst -sha256 -verify public-key.pem -signature patch.sig patch.zip

C# ですと “RSAPKCS1SignatureDeformatter.VerifySignature” などを用いて
実現するのも良いと思います。

参考例 / 引用元 : デジタル署名を作成、検証する – .NET Tips (VB.NET,C#…)
https://dobon.net/vb/dotnet/string/digitalsignature.html
( 一部コードの修正などを筆者側で加えています )

using System;
using System.Text;
using System.Security.Cryptography;

/// <summary>
/// デジタル署名を作成する
/// </summary>
/// <param name="message">署名を付けるメッセージ</param>
/// <param name="privateKey">署名に使用する秘密鍵</param>
/// <returns>作成された署名</returns>
public static string CreateDigitalSignature(
    string message, string privateKey)
{
    //メッセージをバイト型配列にして、SHA1ハッシュ値を計算
    var msgData = Encoding.UTF8.GetBytes(message);
    var hashData = (new SHA1Managed()).ComputeHash(msgData);

    //RSACryptoServiceProviderオブジェクトの作成
    var rsa = new RSACryptoServiceProvider();
    //秘密鍵を使って初期化
    rsa.FromXmlString(privateKey);

    //RSAPKCS1SignatureFormatterオブジェクトを作成
    var rsaFormatter = new RSAPKCS1SignatureFormatter(rsa);
    //署名の作成に使用するハッシュアルゴリズムを指定
    rsaFormatter.SetHashAlgorithm("SHA1");

    //署名を作成し、文字列に変換して返す
    return Convert.ToBase64String(rsaFormatter.CreateSignature(hashData));
}

/// <summary>
/// デジタル署名を検証する
/// </summary>
/// <param name="message">署名の付いたメッセージ</param>
/// <param name="signature">署名</param>
/// <param name="publicKey">送信者の公開鍵</param>
/// <returns>認証に成功した時はTrue。失敗した時はFalse。</returns>
public static bool VerifyDigitalSignature(
    string message, string signature, string publicKey)
{
    var msgData = Encoding.UTF8.GetBytes(message);
    var hashData = (new SHA1Managed()).ComputeHash(msgData);

    //RSACryptoServiceProviderオブジェクトの作成
    var rsa = new RSACryptoServiceProvider();
    //公開鍵を使って初期化
    rsa.FromXmlString(publicKey);

    var rsaDeformatter = new RSAPKCS1SignatureDeformatter(rsa);
    //署名の検証に使用するハッシュアルゴリズムを指定
    rsaDeformatter.SetHashAlgorithm("SHA1");

    //署名を検証し、結果を返す
    return rsaDeformatter.VerifySignature(hashData,
        Convert.FromBase64String(signature));
}

余談ですが、今回脆弱性を発見するきっかけになったのは
「メタ情報」に容易にアクセスできたことでした。

今回、ソフトウェアが Windows の Proxy 設定をアップデート時に使用するようになっており、プロキシを挟んだ途端に上記のように通信が確認出来る状態になりました。

Windows では Proxy 設定は、グループポリシ等で制約している場合を除き、

KEY : HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings
Element: ProxyServer 
Type : REG_SZ

またこのとき、アップデートの通信に関し HTTPS 通信で行われていましたが、
HTTPS 通信の証明書の検証は行われておらず、改竄した内容がそのまま信頼される状態になっていました。

HTTPS 通信の証明書の検証は、一般的に検証をした方が不必要なリスクから守ることができる場合が多いため行った方が良いです。

ただ今回のような「アップデータ」の場合は、検証に失敗したことによってアップデートが出来なくなるという可能性も考えられますので、どちらのリスクを取るかは検討した方が良いかと思いますし、先述のファイルに対する電子署名を信頼して、HTTPS 通信の証明書の検証はしないという選択もありだと思います。


また、今回の脆弱性を外部から悪用する場合、DNS の変更か Proxy の変更が必要でした。
(その他の方法もありますが割愛します)

世の中を見渡してみますと「インターネットの高速化」などを偽って、ローカルの DNS 設定を書き換えてしまうマルウェア ( 例:ユーザーに気付かれずにひそかにDNS設定を変えてしまう「DNSアンロッカー」 / マルウェア情報局 ) や 脆弱なルータを突いて設定を書き換えてしまう攻撃 (例: ルーターをハッキングし、利用者をフィッシングサイトへリダイレクトする攻撃 / カスペルスキー公式ブログ ) も出ている背景がありますので、
必ずしもかなり難しい攻撃ではないと考えられます。

ですので、DNS や HTTPS の証明書といった経路に対して信頼を付与するのではなく、通信内容(今回の場合はアップデートのファイル)について「エンド-エンド」での信頼を前提として、その上で HTTPS の証明書検証をするといったような形がよいと考えます。


2.ソフトウェアアップデート処理が高権限で走っている

アップデートの処理がコンピュータ内で一番高い権限を持っているプロセスで実行されており、アップデート処理を細工することで任意のプログラムを一番高い権限で実行できるようになっていました。

アップデートファイルの展開処理などが高い権限で実行されている場合
アーカイブファイルの細工 (例 : Zip Slip / ディレクトリトラバーサルが発生する zip ファイルを用いて、意図しないファイルの書き換えを行えてしまう ) をはじめとした色々な要因によってファイルの意図しない変更が発生する可能性があります。

対策としては、ファイルの検証は低い権限で行うなど、不必要に高い権限を与えないように設計するのが良いかと思います。

また、今回の例ではありませんでしたが
アップデートの処理が書かれたプログラム(アップデータ)を配布ファイルに含めている実装もいくつか見られます。

この場合、可能であれば既に稼働中のプログラムに処理を入れ(ファイルの書き換えを稼働中の信頼出来るプログラムによって行うようにし)権限を徐々に与えるという形が良いでしょう。


そばが食べたい一心で書いていましたので、
途中誤字や設計としてそれはオカシイ!という点もあるかも知れません。
指摘等は受け付けておりますので、気兼ねなくメッセージをいただければ幸いです。


余談ではありますが、「脆弱性情報」と聞くとその製品を叩いたり「こんな危険なものは使えない!」と思われる方がいらっしゃると思うのですが、それは違うと思っています。

逆に「実はあるのに、みんな黙っている世界」というのを考えてみたら、そっちの方が怖いかと思いますし、それでしたら「どういうことがあって狙われる、こういう風にすることで修正できる」という話を優しく広げていくことが大事なのではないかなと。

私自身、元々ソフトウェアディベロッパーから、今は情報セキュリティ関係を主軸にしていますが、正直「情報セキュリティの人たちすごく怖いな・・」というのはかつてありまして、今も時々感じることはあります。

「これが分かってて当然」「これが出来ないのはオカシイ」という話がよく出てくる世の中ではありますが、失敗しても治していくことができればそれでいいんじゃないかな、とかつての経験とかを振り返りながら思う今日この頃です。
(何かありましたら気兼ねなく聞いてもらえればと!)

それでは、よいお年をお迎えください!

VAIO SX12 ( 2020年10月モデル ) も SSD 換装が可能。

どうもみむらです。
新しい PC を買ったら何をするか、それは分解に決まっています [要出典]

今回、最新の VAIO SX12 (2020年10月モデル) を SSD 換装前提で購入しましたので
例のごとくバラしてみます。

バラしてみる


結論としてはこの画像を見れば一目瞭然です。
(画像を撮り忘れたので SSD は換装済みのものです)

SX12 の場合、裏のネジを外すと、キーボードトップ側が外れるようになっています。
(ですので、VAIO Pro 13 の頃のような、USB 端子の部分を広げながら外したり・・等々がありません。楽ちん!)

今回は M.2 (SATA) 接続モデルを購入し、 M.2 (PCI-e) に換装しましたが、
キチンと PCI-e として認識してくれます。

もちろん、換装するためにバラしたりすることによって、
保証がなくなったり故障したりしたとしても私としては保証出来ません(自己責任)が
安くPCを買いたい方や、ある程度のスキルがあるかたには是非お勧めです!

取り急ぎメモまで・・・。

PrimoCache を使って Intel の最新チップセットでも HDD を SSD を使って高速化する

どうもみむらです。
先日 PC が故障しまして第9世代の Intel CPU (i7-9700) を用いて組み直していました。

故障する数日前からビープ音(長音)の連続で起動に失敗したりしていたので
なんか気持ち悪いなとは思っていたのですが。。

テレワークが推奨されており、お仕事に甚大な影響が出ることもありましたので
ツクモ電機さんのレジの横にあるリーダーにクレジットカードをシャコシャコッと何度も差し入れしてきました。。来月の引き落としに今から震えています。

ただ今回買い換えを行ってみて
Intel Smart Response Technology (iSRT, HDD を SSD キャッシュで高速化するやつ) が
Intel Optane Memory との組み合わせ専用かつ、RAID 不可に変更
されており
iSRT を使って RAID-5 を高速化していた身としてはかなりショックでした。。

では iSRT の代替としていいものは無いのかと探していたところ
PrimoCacheという良さそうなものを見つけたので試してみました。


PrimoCacheとは

HDD を SSD でキャッシュして高速化するソフトです。
まさしく Intel の Smart Response Technology で実現していたものを実現してくれます。

ほかにも FuzeDrive (AMD 環境では StoreMI という名前で提供されています) というソフトなどもありますが、いくつか試してみたところ PrimoCache がよさそうな感じでした。

PrimoCache は Romex Software 社の製品で、1つ当たり $29.95 の製品になっています。
https://www.romexsoftware.com/en-us/primo-cache/

なお私自身はメーカーさんから
今回の記事に関して何か支援を受けてはいませんのであしからず。。

PrimoCache の設定をする

左上のボタンを押してドライブを追加します。

図3 : ドライブの追加ボタン

次の画面で「高速化したいドライブ」を選択します。

図4: 高速化したいドライブを設定。今回は Intel RAID 1 ドライブ。

次にキャッシュの方法についての設定です。
Level-1 Cache が “DRAM 上” のキャッシュ、 Level-2 Cache がストレージとなります。

またキャッシュについては文字通り Level1, Level2 の階層構造になっています。

今回の場合は SSD キャッシュのみを有効にするため、
Level-1 Cache は 0MB に設定します。

図5: Level-1 Cache を 0MBに設定する

次に Level-2 Cache を設定するために、画面中央の小さなボタンを押下します。

図6: Level-2 Cache の設定ボタン

ボタンを押下すると次のような画面が表示され
キャッシュ用のドライブを選択する画面になります。

今回は Intel Optane Memory を使うため
INTEL MEMPEK.. で始まるドライブを選択します。

注:もちろん Intel Optane Memory を使わずに通常の SSD でも利用可能です。
注2:認識されている容量が小さい場合は何らかのパーティションを作ってから再試行してみてください。

図7: キャッシュ用ドライブを設定している図¥

設定後、 “Size” を “MAX” に変更し、その横にあるボタンを押下します。

図8: キャッシュ設定を行う

特に何もなければ
“Individual Read/Write Cache Space” のチェックを無効にします。

これが ON の時はそれぞれのキャッシュが設定した割合で行われますが
無効の場合は割合を気にせずに Read / Write キャッシュを行うようになります。

図9: Read/Write Cache の割振設定を無効にする。

最後にお好みで Defer-Write (遅延書き込み) の設定を行います。

遅延書き込みを有効にすると応答速度が速くなりますが、
ディスクへの書き込み(キャッシュ内容の反映)が遅くなります。

図10: 設定がほぼ完了した図

設定が完了したら “Start” を押下します。

画面UI について

稼働を始めると下記のようにキャッシュ率などが表示されます。

図11: 稼働が開始した画面

ある程度動きはじめたら、画面を閉じて仕事に戻りましょう。


冒頭のとおり PC の故障で買い替えたところ
Intel RAID 構成に対する SSD キャッシュが使えなくなって呆然としていたところ
こういうソフトを見つけることができてよかったです。。!

また前回の構成では Intel RAID の RAID-5 だったのですが
iSRT が有効な環境だと最新のマザーボードや mdadm ではマウントできず焦りましたが
dmraid を使うことでマウントができました。

PrimoCache の場合は Intel RAID と SSD Cache が別の機構なため
同じようにマウントできなくなることは(きっと)ないと思いますし安心です・・!

ではでは、素敵なテレワークライフを!