I know I believe in nothing but it is my sweet nothing.:2018年05月分

2018/05/01(Tue)

[プログラミング] debugging MiniDLNA(ReadyMedia) その3

まとも技術者たるものDLNAの仕様書つーかガイドラインも読んでおくべきなのかもしれんけど、ワイは技術者じゃない上にそもそもメンバー以外お断りのよくあるパターンなので以下略。 今回のトラブルも現時点ではDLNAの仕様とは無関係の可能性が高いしな。

ちなみにUPnP AVの方は IEC 62481-1:2017でいつものスイスフランでお布施すれば買えるもよう、なおそんなお金があったらルーター買い換えるので以下略。

Miniというだけあってソース少ないので読むのは苦にならないと思う、コードも綺麗な部類でしょう(*BSD性パラノイア患ってたら知らん)

$ ls
albumart.c            getifaddr.c        log.c            minixml.c   sql.h              upnpdescgen.h         upnpsoap.c
albumart.h            getifaddr.h        log.h            minixml.h   tagutils           upnpdescstrings.h     upnpsoap.h
codelength.h          icons.c            Makefile         options.c   testupnpdescgen.c  upnpevents.c          utils.c
config.h              image_utils.c      metadata.c       options.h   tivo_beacon.c      upnpevents.h          utils.h
config_R6300v2.h      image_utils.h      metadata.h       playlist.c  tivo_beacon.h      upnpglobalvars.c      uuid.c
config_WNDR4000AC.h   inotify.c          minidlna.c       playlist.h  tivo_commands.c    upnpglobalvars.h      uuid.h
config_WNDR4500REV.h  inotify.h          minidlna.conf    po          tivo_commands.h    upnpglobalvars.h.old
CVS                   INSTALL            minidlnapath.h   README      tivo_utils.c       upnphttp.c
daemonize.c           LICENCE            minidlnatypes.h  scanner.c   tivo_utils.h       upnphttp.h
daemonize.h           LICENCE.miniupnpd  minissdp.c       scanner.h   TODO               upnpreplyparse.c
genconfig.sh          linux              minissdp.h       sql.c       upnpdescgen.c      upnpreplyparse.h

命名も簡潔でどこから読めばいいかgrepなどのツールに頼らずとも明確で人はかくあるべしという感じ、こういう整理ができない無能を滅ぼさないからプログラミング業界は地獄なんだよな。

とりあえずls叩いただけでまずワイが読むべきなのは優先順位的に

であって後は補助的に

あたりを読めばいいんだろうなーと理解できるというのはほんと素晴らしいことだと思うよ。

そしてこの優先順位どおりにscanner.hを開くと

 62 int
 63 is_video(const char * file);
 64
 65 int
 66 is_audio(const char * file);
 67
 68 int
 69 is_image(const char * file);
 70
 71 sqlite_int64
 72 get_next_available_id(const char * table, const char * parentID);
 73
 74 int
 75 insert_directory(const char * name, const char * path, const char * base, const char * parentID, int objectID);
 76
 77 int
 78 insert_file(char * name, const char * path, const char * parentID, int object);
 79
 80 int
 81 CreateDatabase(void);
 82
 83 void
 84 start_scanner();

とあってこのプロトタイプ宣言を読むだけで

何をしているのか把握できる理想的な命名規則ですな(スネークケースとキャメルケースが混在してるだけで発狂するパラノイアは知らん)、素晴らしい。 命名はコメントに勝るんですわ、命名に失敗してグダグだコメント書くやつは無能ってはっきりわかんだね。

そしてお次はinotify.hを開くと

  2 int
  3 inotify_remove_file(const char * path);
  4
  5 void *
  6 start_inotify();

とあってこちらもプロトタイプ宣言を読むだけで

とわかる、なんか今度は微妙にわかりづらくなってるのはそれがあなたがLinuxの inotify(7)をご存じないか、inotifyという命名がLinuxというかUNIX文化の連中のセンス壊滅的かつグロ放送禁止そして現代美術の醜さそびえ立つクソということですわ。

今回も相変わらず技術的な中身は無いけども、命名が下手糞なプログラマという存在はそれだけで死罪であの世でハートマン軍曹に罵られてきやがれという事が判っていただければそれでヨシ。

[プログラミング] debugging MiniDLNA(ReadyMedia) その4

前回ヘッダから発見したスキャン開始のコードと思われるstart_scanner()が呼ばれてるのは以下の場所

 849 /* === main === */
 850 /* process HTTP or SSDP requests */
 851 int
 852 main(int argc, char * * argv)
 853 {
...
 937                 if( CreateDatabase() != 0 )
 938                 {
 939                         DPRINTF(E_FATAL, L_GENERAL, "ERROR: Failed to create sqlite database!  Exiting...\n");
 940                 }
 941 #if USE_FORK
 942                 scanning = 1;
 943                 sqlite3_close(db);
 944                 scanner_pid = fork();
 945                 open_db();
 946                 if( !scanner_pid ) // child (scanner) process
 947                 {
 948                         start_scanner();
 949                         sqlite3_close(db);
…

937行目のCreateDataBase()でSQLite3データベースファイルを作成した後に944行目でfork、そして948行目からの子プロセス側で実行される処理部で呼び出している。

ここでわざわざforkしてるのはstart_scanner()はとても時間かかる処理なので、終わるまで何もできないよりも裏でやらせたほうが都合がいいからでしょうな。 デバッグする側としてもstart_scanner()の処理は専用プロセスで処理されてるので親が何やってるのか読む必要なくなるのでこれは楽。

そして今起きてる現象との整合性もばっちりよね。

って状態なので、このスキャン専用の子が何らかの理由で突然死したけども親はそれに気付いてないと想像つきますな、ひゃーネグレクト。

実際コード読んでも親と子の間でpipe(2)なんかのプロセス間通信を使ってる形跡が無さそうなんだよね、もしかすると

可能性はあるかも知れんけどね、まぁおいおい読んできゃ判るのでしょ(慢心)。

それでは実際にstart_scanner()の実装を読んでみる。

844 void
845 start_scanner()
846 {
847         struct media_dir_s * media_path = media_dirs;
848         char name[PATH_MAX];
849
...
860         while( media_path )
861         {
...
862                 strncpy(name, media_path->path, sizeof(name));
863                 GetFolderMetadata(basename(name), media_path->path, NULL, NULL, 0);
864                 ScanDirectory(media_path->path, NULL, media_path->type);
865                 media_path = media_path->next;
866         }

847行目にあるmedia_dir_s構造体はスキャン対象のディレクトリ(複数) *1を保持しているlinked list、これを860~866行目でループ処理している。

862行目では次行で呼ばれているbasename(3)の為にmedia_dirをname変数にコピーしている。 これ普段glibc相手にプログラムしてる人は忘れがちなんだけど、basename(3)やdirname(3)引数を破壊することをPOSIXは許容しているので必要な処理なのよね。

そして862行目、よく訓練されたプログラマならstrncpy(3)使ってることにブチ切れてバグレポート叩きつけるレベル。 strncpy(a, b, n)はbの文字列長がnを超える場合、aは終端処理されないからオーバーラン系のバグの温床になるのでこの使い方はアウト。 どうするのが正しいかは信じる宗教によって変わるので null 終端バイト文字列を不注意に切り捨てないあたり読んでどうぞ。

そしてこのループ処理の中で

という2つの処理を呼び出していることが判る。

そんでGetFolderMetadataは前回副次的に読めばいいんでねと書いたmetadata.[ch]にある。

 313 sqlite_int64
 314 GetFolderMetadata(const char * name, const char * path, const char * artist, const char * genre, sqlite3_int64 album_art)
 315 {
 316         int ret;
 317
 318         ret = sql_exec(db, "INSERT into DETAILS"
 319                            " (TITLE, PATH, CREATOR, ARTIST, GENRE, ALBUM_ART) "
 320                            "VALUES"
 321                            " ('%q', %Q, %Q, %Q, %Q, %lld);",
 322                            name, path, artist, artist, genre, album_art);
 323         if( ret != SQLITE_OK )
 324                 ret = 0;
 325         else
 326                 ret = sqlite3_last_insert_rowid(db);
 327
 328         return ret;
 329 }

おおう…なんもGetなんてしてないやん詐欺やんこんなの、Getすると思わせてINSERTするなんて卑怯だわ。

これはどうしてかというとディレクトリでない他のファイル形式の場合は

とファイルから情報をGetする処理があるからの命名なんだろう、なので本当はGetとInsertを分割すべきなんだけどな。

つまりここのGetFolderMetadataは単に検索開始するディレクトリ名をDETAILSテーブルに登録してるだけってこと。 それならScanDirectoryの中で呼べばいい気がするんですけどね…

次回はScanDirectoryの実装を読んでいく予定。

*1:設定ファイル(minidlna.conf)のmedia_dirで指定した値やね。

2018/05/02(Wed)

[プログラミング][今すぐ窓から投げ捨てろ] debugging MiniDLNA(ReadyMedia) その5(最終回)

いきなりだけどScanDirectoryのコードを読んだら原因判明してしまった…

719 #define MAX_FILE_NUMBER 25000
720 void
721 ScanDirectory(const char * dir, const char * parent, enum media_types dir_type)
722 {
...
738     if(fileno >= MAX_FILE_NUMBER) // stop scanner
739         return;
…
812         if( (type == TYPE_DIR) && (access(full_path, R_OK|X_OK) == 0) )
813         {
814             insert_directory(name, full_path, BROWSEDIR_ID, (parent ? parent:""), i+startID);
815             sprintf(parent_id, "%s$%X", (parent ? parent:""), i+startID);
816             ScanDirectory(full_path, parent_id, dir_type);
817         }
818         else if( type == TYPE_FILE && (access(full_path, R_OK) == 0) )
819         {
820             if( insert_file(name?name:namelist[i]->d_name, full_path, (parent ? parent:""), i+startID) == 0 )
821             {
822                 fileno++;
823                 if(fileno >= MAX_FILE_NUMBER){
824                     /*stop scanner*/
825                     n = 0;
826                 }
827
828             }
829         }

おわかりいただけただろうか、MAX_FILE_NUMBER(=25000)を超えたらそこで終了なんやねこれ。 要するに初期スキャンはファイル25000上限が仕様ちゅーこと、なんやこれクソが。

前に書いた「だいたい28000個くらい処理したところで止まる」という現象は

だったからその数字を出したのね、そしてその件数でソースをgrepしてもヒットしないからデバッグ開始したんだけど。

でもDETAILSテーブルからファイルでない余計なものを除いた件数を出力すると

$ sqlite3 files.db
SQLite version 3.21.0 2017-10-24 18:55:49
Enter ".help" for usage hints.
sqlite> select count(*) from DETAILS where SIZE is not null;
25000
sqlite>

はい25000で完全一致、お疲れ様でした。

いやー萎えるせっかく久々にdebugする気になったのにこのオチはねーよなジャンプの10週打ち切り漫画の最終回 *1より酷い…

果たしてこのクソコードは何がやりたかったのかは推測するしかないんだけども

あたりなのかなぁ、最新版のコード(1.2.1)ではこの制限は削除されてるのでまず前者は無さそうではあるんだけども。

ということでNETGEAR 6300v2に付属するバージョンのReadyDLNA=MiniDLNA(ReadyMedia)で25000ファイルを超える音楽・写真・動画ファイルが扱えないのは仕様ですってことやねん。 今すぐ窓から投げ捨てろシリーズ入りやなぁこれ。

*1:ちなみにワイはジャンプ買ったこと一度も無かったりする

[やきう] 今永炎上

肩の故障がルーズショルダーだって報道と前回当番時のクソフォームと投球テンポで察していたんだけど、これ今永は去年の日シリ登板のレベルには二度と復活せんなこれ。

そんで点取れない元凶の筒香の不調やけどこれも怪我隠し定期やろね。しれっと去年の日シリの前に首のヘルニアがなんて記事でたし、オープン戦のフォーム改造も首の痛み対策や。2016年二冠王レベルの復活はないな。

ちゅーことで優勝なんぞ監督代えてもカタツムリでまた一回り世代交代するまで無理や、やはり98年日本一の時の波留「また38年後お会いしましょう」の呪いか…

つーか同じチーム(セだと阪神)にまったく勝てないってのさすがにスコアラー代えた方がええのでは。 その辺が広島との差なんだよな、今日も巨人山口メンバーの死球の時に乱闘仕掛けにいってあの弱メンタルにきっちり精神攻撃与えとったしな。

[今すぐ窓から投げ捨てろ] NETGEAR R6300v2のReadyDLNAには25000ファイル上限がある件

さっき原因を突き止めたNETGEAR R6300v2の25000ファイル制限はどうもMiniDLNA(ReadyMedia)のメインストリームには一度も入ったことの無い制限で、機種独自の制限だなこれ。

んで25000をキーワードに検索かけるとオーストラリアのよくわからん掲示板に インデックス処理が遅いしクエリの結果が大き過ぎるなんて話がヒットして、途中でファイル数25000を上限にすれば改善するなんて発言がみつかるので、この男がサポートにバグレポとして上げた可能性が、余計な事を…

という事で性能問題ということなんだが、これまず起動時スキャンが遅い問題は

という実装が悪いよねとしかいいようが無いよな、そして上限を設けるにしても

ちゅーこと。

そんでクエリ結果が大き過ぎるってーのも

ReadyDLNA: R6300v2
  | 
  +- Music
      |
      +- Album
      |
      +- All Music (25000) ← これは酷い
   |
      +- Artist
   |
      ...

の「All Music」みたいなノードを用意して、全ての曲をプレイリストとして返すようクエリを許す設計が悪いとしかいいようが無いですわ。 全曲垂れ流したいなんてのはこの層で実装するんじゃなくてプレイリストを順に再生していけば良いし、そもそも25000曲全てを曲名の文字コード順に並べたリスト *1なんて100%不要と言い切れる。 これ実装してる人あまり音楽好きではないのでは…

少なくともMusicBeeのUPnP/DLNAサポートプラグインはそんな頭の悪い設計のノードは持ってない。

MusicBee
  | 
  +- 音楽
      |
      +- Album Artists
      |
      +- Albums
   |   |
      |    +- #
      |    |
      |    +- A
      |    |
      |    ...
      +- Artists
   |
      +- Composers
   |
      +- Genres
   |
      +- Years

どのサブノードも件数が爆発しないよう、アルバムタイトルなら頭文字を入れるとか対策してあるよね、つーかこうなってないと聴きたい曲なんか探せないよ。

ということでMiniDLNA(ReadyMedia)は音楽好きにはお勧めできませんって結論やな、まぁ自分で改造するならコード量も少なく読みやすい部類でRDBMS使ってるから変更に対して柔軟ではあるんだけど…

*1:アルバムとベストアルバムで同じ曲が重複して入ってるケースだと同じ曲が連続して流れるわけでな、New OrderやDavid Bowieみたいなリマスタ商法アーティストの曲が延々と…

2018/05/03(Thu)

[プログラミング] ひたすら MiniDLNA(ReadyMedia) のコードにダメ出ししていく(その1)

早々にバグ(というかクソ仕様)が特定できてしまい不完全燃焼、それならガソリン撒いて跡形も無く燃やしてしまえばいいと、所沢山賊隊こと西武打線がワイに囁いている。 BGMは Big Black/Keroseneでお願いします。

あ、NETGEAR R6300v2同梱版はもう窓から投げ捨てたので今回からはgit repositoryの最新版ベースで。

@ディレクトリの走査には必要が無い限り scandir(3) でなく opendir(3) + readdir(3)を使う

まず指定ディレクトリ以下の子要素を走査するに scandir(3) を使ってるのって微妙だねー感がある。

721 static void
722 ScanDirectory(const char *dir, const char *parent, media_types dir_types)
723 {
...
735                         n = scandir(dir, &namelist, filter_avp, alphasort);

そもそも scandir(3) って

  • 第1引数で指定されたディレクトリ以下に存在するファイル一覧を
  • 第3引数で指定された条件にマッチするファイルのみ選んで
  • 第4引数で指定されたソート関数を使って並び替え
  • malloc(3)によってメモリ確保した配列に↑の結果を格納し、第2引数にポインタをセットして返却する

という関数なので

  • 同一ディレクトリの下に
  • 大量にファイルが置かれる

みたいなユースケースには不向き、そして音楽ファイルや画像ファイル置き場なんてのは正にそういう使い方なのよね。

なので"Mini"を自称するのであれば opendir(3) + readdir(3) を使うべきとこかな。

サンプルコードだけど、↓は

#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
int
main(void)
{
	struct dirent **ent;
	int len, i;

	len = scandir(".", &ent, NULL, NULL);
	if (len < 0)
		abort();
	for (i = 0; i < len; ++i) {
		if (ent[i]->d_name[0] == '.')
			continue;
		printf("%s\n", ent[i]->d_name);
	}
	free(ent);
	exit(EXIT_SUCCESS);
}

↓と書き直せる。

#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
int
main(void)
{
	DIR *d;
	struct dirent *ent;

	d = opendir(".");
	if (d == NULL)
		abort();
	while ((ent = readdir(d)) != NULL) {
		if (ent->d_name[0] == '.')
			continue;
		printf("%s\n", ent->d_name);
	}
	exit(EXIT_SUCCESS);
}

そもそも scandir(3) の中身って opendir(3) + readdir(3) だし、参照 src/lib/libc/gen/scandir.c

 85 int
 86 scandir(const char *dirname, struct dirent ***namelist,
 87     int (*selectfn)(const struct dirent *),
 88     int (*dcomp)(const void *, const void *))
 89 {
...
 97         if ((dirp = opendir(dirname)) == NULL)
 98                 return -1;
...
108         while ((d = readdir(dirp)) != NULL) {

よって第4引数で指定するソート関数を使う用事が無いなら無駄でしかない。

元コードはソート関数として alphasort(3) を指定してるので、敢えてscandir(3)使った可能性は残ってる。alphasort(3) は

  • ファイル名をstrcoll(3)で比較し
  • qsort(3)でソートする

という関数でGNU拡張、strcoll(3)はLC_COLLATEつまり文字照合順序に応じてソートを実行する。

よって迂闊に書き換えてしまうと処理の順序が変わってしまうんだけど、ここで冷静に脳味噌を働かせて考えると

  • ソートしたいのはフォルダ名でなくID3タグ中の曲名やアーティスト名なので、ここで alphasort(3) 指定しても全く意味が無い
  • もちろん「Brows Folders」の検索結果はフォルダ名でソートされてる必要があるけど、それは今ソートする必要はゼロ
  • SQLite3 という RDBMS を使うことによるメリットを存分に享受して出力時に ORDER BY 使えばいいだけの話

ということ、やっぱりクソコードやんけ!

そもそも英語圏だとアーティスト名のソートって定冠詞(The)を無視する必要があったり、日本語圏なら漢字コード順でなく読み仮名順でのソートしないとならない。 なのでその辺ちゃんと実装しようと思ったら大変なんですよな。

@もし移植性に目を瞑れるのであれば opendir(3) + readdir(3) ではなく fts(3) の使用も検討する

readdir(3) は opendir(3) したディレクトリ直下の情報しか帰ってこないので、サブディレクトリを再帰的に処理したければコードも再帰を使って書く必要があり、ヘタクソなコードだとスタックオーバーフローの温床になりがち。

もっと高級言語だと指定したディレクトリ以下を再帰的に走査し要素をイテレーターとして返すAPIがありユーザーが再帰コード書く必要が無い、例としては

あたりがそう、実はCでも標準ではないんだけど似たような関数があってそれが表題の fts(3)なんよね。

簡単なコード例を示すと

#include <sys/types.h>
#include <sys/stat.h>
#include <fts.h>
int
main(void)
{
	FTS *d;
	FTSENT *ent;
	char *paths[] = { ".", NULL };

	d = fts_open(paths, FTS_PHYSICAL, NULL);
	if (d == NULL)
		abort();
	while ((ent = fts_read(d))) {
		if (ent->fts_level == FTS_ROOTLEVEL)
			continue;
		switch (ent->fts_info) {
		case FTS_D:
		case FTS_F:
			printf("%s\n",  ent->fts_name);
			break;
		}
	}
	exit(EXIT_SUCCESS);
}

みたいに使う、メリットとしては

  • バグの温床になりがちな再帰コードを書かずとよい
  • FTSENT には struct dirent だけでは取れないような情報も定義されてるので 改めて lstat(2) 呼ばんでも済む(TOCTTO対策だけは忘れずに)
  • fts_open(3) の第3引数には scandir(3) みたいにソート関数も指定できる

という至れり尽くせりの高機能、その分だけ仕様が複雑ではあるんだけど使うだけの価値はある *1

それに移植性は無いといえども

で動きゃ充分よね、AIXとHP-UXは隅っこで腹筋してろWindowsは好きな寿司ネタでも書いとけ。

@fts(3) ほど高機能である必要が無いのであれば、移植性のある nftw(3) を使う

つーか本当は POSIX に fts(3) によく似た nftw(3) - New File Tree Walkという関数があって、広い移植性を考えたらそっちを使う方が好ましい。

#include <sys/stat.h>
#include <sys/types.h>
#include <ftw.h>
#include <stdio.h>
#include <unistd.h>

static int
do_walk(const char *path, const struct stat *st,
    int flag, struct FTW *ftwp)
{
	switch (flag) {
	case FTW_D:
	case FTW_F:
		printf("%s\n", path);
		break;
	}
	return 0;
}

int
main(void)
{
	nftw(".", &do_walk, sysconf(_SC_OPEN_MAX), 0);
	exit(EXIT_SUCCESS);
}

ただし

  • コールバック関数(コード例だとdo_walk)にユーザー定義のパラメータ渡す方法が無い
  • グローバル変数あるいはスレッドローカル変数を使わざるをえない

というデザインが単純にダメ *2、そもそも中身 fts(3) の wrapper として実装されてるから使う意味が…

元々 fts(3) は POSIX 入る予定だったんだけどねぇ、マニュアルにも恨み言が書いてあったり。

 STANDARDS
      The fts utility was expected to be included in the IEEE Std 1003.1-1988
      (``POSIX.1'') revision.  But twenty years later, it still was not
      included in the IEEE Std 1003.1-2008 (``POSIX.1'') revision.

というか流し読みしてるとPOSIX:2008に入ってるように空目するからSTANDARDSに書くべくじゃねぇなこれ、CAVEANTSなりBUGSに移せや。

@特に理由が無いならシンボリックリンクは安全の為に無視する

次に気になったのが以下のコード

629 static int
630 filter_type(scan_filter *d)
631 {
632 #if HAVE_STRUCT_DIRENT_D_TYPE
633         return ( (d->d_type == DT_DIR) ||
634                  (d->d_type == DT_LNK) ||
635                  (d->d_type == DT_UNKNOWN)
636                 );
637 #else
638         return 1;
639 #endif
640 }

scandir(3) のフィルタに指定されてる関数のひとつなんだけど、DT_LNK つまりシンボリックリンクを除外してないのよね。

シンボリックリンクを無視するしないはあくまでアプリケーション側のポリシーの問題だけど、いったん受け入れると決めたなら注意深く実装しないと

  • シンボリックリンクが循環参照していると再帰しながらディレクトリを走査する関数なんかでスタックオーバーフローが発生する可能性がある
  • 本来はお見せしてはならないファイルにシンボリックリンクを貼るなどの方法で重要情報が流出する可能性がある

という可用性や機密性に関するセキュリティ問題が容易く発生するんですわ。

正直なところ音楽ファイルにシンボリックリンクが意味を持つとは思えないよね、ファイルシステム層でなくアプリケーション層つまりプレイリスト機能でいくらでもエイリアス切れるわけでな。 なのでこれはどんな判断だと思うけど、記事のネタ的にはシンボリックリンク扱う方が含蓄あるので以下その線で話を進める。

@どうやってシンボリックリンクの循環参照を検出するか

MiniDLNAでは↓のコードで実現してるらしい

482 int
483 resolve_unknown_type(const char * path, media_types dir_type)
484 {
...
490     if( lstat(path, &entry) == 0 )
491     {
492         if( S_ISLNK(entry.st_mode) )
493         {
494             if( (len = readlink(path, str_buf, PATH_MAX-1)) > 0 )
495             {
496                 str_buf[len] = '\0';
497                 //DEBUG DPRINTF(E_DEBUG, L_GENERAL, "Checking for recursive symbolic link: %s (%s)\n", path, str_buf);
498                 if( strncmp(path, str_buf, strlen(str_buf)) == 0 )
499                 {
500                     DPRINTF(E_DEBUG, L_GENERAL, "Ignoring recursive symbolic link: %s (%s)\n", path, str_buf);
501                     return type;

うーんこの、全然できとらんやんけ!

$ mkdir foo
$ cd foo
$ ln -sf . foo
$ ls -all foo
lrwxr-xr-x  1 tnozaki  tnozaki  1 May  2 12:00 foor -> .

みたいな循環参照は検出できない、検出可能なのは

$ ln -sf foo foo
$ ls -all foo
lrwxr-xr-x  1 tnozaki  tnozaki  1 May  2 13:14 foo -> foo

みたいにシンボリックリンクが自分自身を参照してるケースだけだわ、これ実体がはじめから存在しないから循環参照ではなく参照切れのケースですわ。

実はさっきの fts(3)を使うメリットにはもう一つあって、循環参照あるいは参照切れを回避しつつリンク先を処理するのも簡単なのよね。オプションに

     FTS_LOGICAL     This option causes the fts routines to return FTSENT
                     structures for the targets of symbolic links instead of
                     the symbolic links themselves.  If this option is set,
                     the only symbolic links for which FTSENT structures are
                     returned to the application are those referencing non-
                     existent files.  Either FTS_LOGICAL or FTS_PHYSICAL must
                     be provided to the fts_open() function.

をセットすれば簡単にチェックできる、fts_read(3) で返ってきた FTSENT の fts_info フィールドに

  • FTS_SLNONE … シンボリックリンクの参照先が存在しない
  • FTS_DC … ディレクトリが循環参照している

がついてたらビンゴなので、エラーなり警告なり無視なりすればいいだけ。

#include <sys/stat.h>
#include <sys/types.h>
#include <fts.h>
#include <stdio.h>

int
main(void)
{
	FTS *d;
	FTSENT *ent;
	char *paths[] = { ".", NULL };

	d = fts_open(paths, FTS_LOGICAL, NULL);
	if (d == NULL)
		abort();
	while ((ent = fts_read(d))) {
		if (ent->fts_level == FTS_ROOTLEVEL)
			continue;
		switch (ent->fts_info) {
		case FTS_SLNONE:
			fprintf(stderr, "ignore symlink without target: (%s)\n", ent->fts_name);
			break;
		case FTS_DC:
			fprintf(stderr, "ignore recursive symlink: (%s)\n", ent->fts_name);
			break;
		}
	}
	fts_close(d);
	exit(EXIT_SUCCESS);
}

これ ntfw(3) の方だと

  • リンク切れは FTW_SLN が帰ってくるので fts(3) と同様の処理ができる
  • 循環参照は安全の為に「完全に静かに無視」されるのでコールバック自体が呼ばれない
  • そのため↑の検出目的には使えない

ちゅーかんじ。

@次回

シンボリックリンク攻撃に対する注意を書こうかと思ってるけど、いつもの通り予定は未定。

*1:詳しい使い方は ls(1)find(1)の実装なんかで使われてるのでそっち読んでくだしあ。
*2:これでもftw(3)を廃止してのnftw(3)なんだよなぁ…

2018/05/04(Fri)

[やきう] 阪神のエサ

というか阪神の体力回復アイテムかなんかか横浜って

[やきう] イチロー

ついに引退かと思わせておいて今年の残りを調整に費やし来年は投手登録でくる可能性もあるよね。

ルースの安打記録はとっくに抜いてるし50歳まで毎年20勝すれば勝利数も余裕よな、大谷よりも先にルースの記録塗りかえるのはイチローやで。

漫画のMAJ○Rだと投手で右肩壊したら左投げにスイッチして左肩も壊したら打者転向とかやってたけど、リアルの場合老眼による動体視力の衰えは打者には致命的だけど、投手にはあまり関係ないので肩肘の消耗のない野手から投手転向は高齢でも成功する可能性はある。

いちおう去年マーリンズで投手温存の為に野手で登板して練習なしに143km/h出したし、来年まで投手調整すれば100マイル(=160km/h)くらいよゆーよゆー(マジキチスマイル)

2018/05/05(Sat)

[やきう] チーム打率 .225 ツツゴー

[プログラミング] ひたすら MiniDLNA(ReadyMedia) のコードにダメ出ししていく(その2)

前回の続き、そもそも UPnP/DLNA って認証/認可が存在しない上に通信も HTTP で暗号化も欠いてる時点でセキュリティ面にダメだしすんのもバカバカしいんですけどね(しろめ) *1、だんだんこんな機能がルーターに載ってる事自体が狂気なのではという気分に…

@シンボリックリンク攻撃とは何ぞや

気をとりなおして前回予告したとおりシンボリックリンク攻撃のお話、簡単に書くと

  • 本来はアクセスできない権限に設定されたファイルを奪取する、あるいは改竄することを目的とした攻撃
  • ↑のファイルのシンボリックリンクを攻撃者が書込権限を持つディレクトリに作成する
  • ↑を管理者権限など特権ユーザで動作するアプリケーションに読み込ませる
  • ↑の結果ファイルを攻撃者がゲットしたり破壊したりあるいは改竄によって誤動作を引き起こせれば攻撃成功

というシナリオ、特に httpd なんかは

  • 80や334443といった特権ポートをbind/listenするのに管理者権限で起動する
  • 共有サーバによるウェブホスティングサービスが広く普及している
  • /home/<username>/public_html のような一般ユーザが書き込み権限を持った公開ディレクトリが存在する
  • 通常ファイルでありさえすればbinary/octet-streamでまず確実に GET できる

という仕様からとっても狙われやすいのよね、Apache だと

  • FollowSymLinks
  • SymLinksIfOwnerMatch

そして Nginx だと

  • disable_symlinks

あたりの設定が意味するところを理解しとらんエンジニャーが設定するとまず事故る *2、数年前に ロリポップがやらかした時にちょっとだけ意識高まったけども。

ゆうてMiniDLNAにシンボリックリンク攻撃を試みたところでたかが音楽・動画・画像ファイルしか奪えないし被害も無いでしょっても

  • 最近コソコソしてる配偶者、ホームディレクトリに怪しげな動画ファイルあるけど、パーミッションが600で再生できない
  • せや、MiniDLNA のスキャン対象のディレクトリにシンボリックリンク貼って中身を確認すればええやんけ!

という家庭内からの攻撃も成り立つのよな、中身がただのエロ動画ならまだしもピーとかピーやったら結婚生活終わるで…慰謝料払えるん…

@必要も無いのにアプリケーションを特権プロセスとして動かさない

まずシンボリックリンク攻撃に関してのよくある誤解としては

  • リンク先ファイルの権限は関係なく
  • 書き込み権限のあるディレクトリさえあれば
  • 自由にシンボリックリンクが作れる

というシンボリックリンクの仕様自体が危険なわけではないということですな、リンク先のファイルに権限が無ければ攻撃のしようが無いわけで。 そこを勘違いした Windows が mklink コマンドの実行に長らく管理者権限を必要とする愚を冒してたのを改め、Windows10 から一般ユーザ権限に引き下げたあたりから察しよう。

失敗の本質は「シンボリックリンク攻撃の対象となるアプリケーションが不必要に高い権限で動いてる」のが悪いのよね。

今回コード監査している MiniDLNA において特権プロセスが必要になるのはざっと眺めた限り

  1. UPnP ではデバイスがネットワークに参加した時に
  2. SSDP(Simple Service Discovery Protocol) でマルチキャストして存在を知らせる
  3. Linuxでは rtnetlink(7) を使ってルーティングテーブルの変更を監視し
  4. ネットワークが変更されたらまた1.から繰り返し

という部分かねぇ、SSDP(UDP) 自体そして

  • デバイスの Web ページ
  • SOAP over HTTP
  • メディアのストリーミング

のための HTTP(TCP) に必要な socket(2) は bind(2)するポートが

  • SSDP … 1900
  • HTTP … 8200(デバイスによって違いこれは MiniDLNA のもの、特に規格では定められてないっぽい)

の非特権ポートとなので特権プロセスである必要は無さそうなんだけどね。

あと rtnetlink(7) だけど移植性無いよね…ってのはさておいて

351 int
352 OpenAndConfMonitorSocket(void)
353 {
354 #ifdef HAVE_NETLINK
355         struct sockaddr_nl addr;
356         int s;
357         int ret;
358
359         s = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
360         if (s < 0)
361         {
362                 perror("couldn't open NETLINK_ROUTE socket");
363                 return -1;
364         }

ここ SOCK_RAW 指定してるけど、非特権プロセスでもエラー起きないしマニュアルの例は SOCK_DGRAM になってるしで、もしかして区別ないんですかねこれ…と思ったら

       Netlink is a datagram-oriented service.  Both SOCK_RAW and SOCK_DGRAM
       are valid values for socket_type.  However, the netlink protocol does
       not distinguish between datagram and raw sockets.

その通りだった、じゃあ特権プロセスである必要無いのでは…

@特権は不要になったら捨てる、あるいは隔離する

これ、MiniDLNA がセキュリティに無頓着なわけではなく特権は破棄してるからセーフ。

 558         uid_t uid = 0;
 559         gid_t gid = 0;
...
 915                 case 'u':
...
 919                                 uid = strtoul(argv[i], &string, 0);
...
 934                 case 'g':
...
 938                                 gid = strtoul(argv[i], &string, 0);
...
1066         if (gid > 0 && setgid(gid) == -1)
...
1070         if (uid > 0 && setuid(uid) == -1)
...

しかしサンプルの起動スクリプト

 1 #!/bin/sh
 2
 3 # chkconfig: 345 99 10
 4 # description: Startup/shutdown script for MiniDLNA daemon
...
30 case "$1" in
31 start)  log_daemon_msg "Starting minidlna" "minidlna"
32         start-stop-daemon --start --quiet --pidfile $PIDFILE --startas $MINIDLNA -- $ARGS $LSBNAMES
33         log_end_msg $?
34         ;;

およびデフォルトの設定ファイル

7 # specify the user account name or uid to run as
8 #user=jmaggard

を使うと root で起動したまんまなんでやっぱりアウトなんやな(チャレンジ制度)。

どうしても特権プロセスが必要なのであれば、その部分だけ別のプロセスとして切り離す 特権分離(privilege separation)というアプリ設計の採用も検討すべきですわね、特権分離についてはまたいずれ書く。

それと特権についてもう少しだけ話をしておくと、伝統的なUNIXにおいては

  • 特権プロセス … 実効ユーザIDが0
  • 非特権プロセス … 実効ユーザIDが0以外

という白か黒かしかない世界で、一般ユーザが動かすけど特権が必要なプログラム(foo)には

# chmod ug+s foo
# ls -all foo
-rwsr-sr-x  1 root  wheel  10 May  6 00:00 foo

として実行ファイルに setuid/setgid ビットを与えるのがしきたりだったんだけど、例えば X Server のような複雑なプログラムにまで与えてしまう *3ケースがあって脆弱性あったときの被害が甚大だったのよね。

なのでよりモダンなUNIX like OSにおいては実行ファイルの setuid/setgid ビットを撲滅すべく

というAPIで特権を細かく制限できるようになってるので、こいつらの利用も検討したほうがいいのかもね。よし全員 *4居るなセーフ!

@次回

だいぶ書く気失せてきたけど、シンボリックリンク攻撃の回避はいうほど簡単でない原因の TOCTTOU(Time Of Check To Time Of Use) という競合問題ついてのお話の予定。

*1:こんなガバガバなもん作るエンジニャー村が送信可能化権ガーとかフィルタリングガーとかもう本来の意味での確信犯なんやな…
*2:つーか .htaccess とか AllowOverride みたいな無駄と有害を混ぜ合わせて20年間コトコト煮込んだ Apache をまだ使ってるの?
*3:なのでXwrapperが生まれたりしてね…
*4:某Nにも systrace(4)というのがあったんだけど、それ自体が権限昇格に悪用できてしまったので消された。

2018/05/10(Thu)

[やきう] 内川2000本

花束贈呈に村田を期待してたんだが(ぉ めでたいけど交流戦と日本シリーズでは打たないで欲しい(トラウマ)

それにしてもおそらく怪我隠してる筒香が再三の得点圏をぶっ潰す6-0で、打率はついに.225目前で去年の中田翔レベルやんけってのが勝てない原因なのに、相変わらず打てん守備専起用して守りきれとか喚いてる連中が多いのがアレやな、加藤球全盛の落合中日のナゴドやきう時代からタイムスリップしてきたんかなあれ。そもそも0点じゃ勝てないんやで。

2018/05/13(Sun)

[スポーツ] F1 第7戦 スペインGP 決勝

シュタイナーHAASチーム代表「MAG-GRO ご期待ください」

プレシーズンテストでは速さをみせるHAASが台風の目といわれてたけど、ボロボロ落ちるパーツのマキビシ作戦とマグヌッセンとグロージャンの危険運転で、毎試合マモノ呼び寄せて正に災害としての台風になってんなこいつら。

2018/05/16(Wed)

[書籍] Tom Wolfe/The Right Stuff

トム・ウルフ死去、映画「 ライトスタッフ」の原作を書いた人、中公文庫から出てた 邦訳ってもう何十年廃刊状態なんですかねクソが。

映画はチャック・イェーガーとマーキュリー・セブンの対比で描かれてるけど、原作はその選考法に反発し辞退するも後にニュー・ナインとしてニール・アームストロングらと同期で宇宙計画に参加したピート・コンラッド(後のアポロ12号船長)からの視点で描かれてたりの違いもあるので、映画しか観てない人はどうにかして入手して読んでどうぞ。

2018/05/17(Thu)

[やきう] 二連勝

メッセには完封目前でいつもの阪神のファストフードかと思ったんだが、さすがに一割打線とロサリオが試合中にサッカーすれば連勝するんやね(しろめ)。

野手はソトロペス宮崎だけでやきうやっとるけど、去年の桑原筒香戸柱梶谷倉本で300打点稼いでようやくギリAクラス滑り込みなのに、今年はその半分でも御の字だとするとまぁBクラス転落よな。

チーム防御率がトップといっても東とか防御率低い先発ほどムエンゴ、そして中継ぎはマシンガン継投で夏場に落ちるのは確実、得点力不足でソト外せなくなるとエスコパットンどっちか二軍、守備も補強したわりに良くなってないし今だけなんやな。

2018/05/18(Fri)

[プログラミング] ひたすら MiniDLNA(ReadyMedia) のコードにダメ出ししていく(その3)

前回の続き。

@Time Of Check To Time Of Use(TOCTTOU)とは何か?

プログラマーでもないとTOCTTOU(トックトゥー)という用語には馴染みないだろうけど、日常生活でも普通に発生する問題なのよね、↓が典型。

要するに

  • 椅子の位置を確認してから(Time Of Check)
  • 椅子に腰掛ける (Time Of Use)
  • このふたつの動作の間、椅子から視線を切ったわずかな瞬間に (To)
  • 攻撃者によって椅子を引っぱるなどのイタズラを仕掛けられる
  • 転倒によって頭部や脊椎を損傷し重大な後遺障害を被る

というやつ。

これは椅子に座るという一連の動作(Transaction)が ACIDでいうとこの

  • 原子性 (Atomicity) … 操作が不可分であること、このケースだと椅子の位置確認と腰掛けるという動作に分割できてしまう
  • 独立性 (Isolation) … 操作が他の操作から不可視であること、このケースだと攻撃者が椅子を操作できてしまう

を欠いているから発生するのよね。

あっこいつアシッドとか原子心母とかいいだしましたよ LSDキメてPink Floyd でも聴いてるんですかね通報。

でだ、UNIXの仮想ファイルシステム(Virtual File System)ってやつもそのへんダメダメ *1なんですな、というかマジで「Unix VFS ACID」で検索したら LSDキメるとOSをUnixに変えたくなるという謎スレがヒットするんですが、お前らいい加減にしろ。

@TOCTTOUを利用したシンボリックリンク攻撃の一例

それでは実例を見てみよう、シンボリックリンク攻撃として最も有名(最古?)である CVE-1999-1388、SunOS 4.1.x の passwd(1) が一般ユーザ権限で /etc/passwd を上書きすることが可能(つまりroot権限を奪える)だった脆弱性。

当時の 惨OS のコードはワイには参照する術が無いので、ベースとなってる 4.1cBSD の src/bin/passwd.cを読んでみる。

 17 char    passwd[] = "/etc/passwd";
 18 char    temp[]   = "/etc/ptmp";
...
 30 main(argc, argv)
 31         char *argv[];
 32 {
...
 53         while (((pwd = getpwent()) != NULL) && strcmp(pwd->pw_name, uname))
 54                 ;
 55         u = getuid();
 56         if (pwd == NULL || (u != 0 && u != pwd->pw_uid)) {
 57                 printf("Permission denied.\n");
 58                 exit(1);
 59         }
 60         endpwent();
...
128         /*
129          * The mode here could be 644 except then old versions
130          * of passwd that don't honor the advisory locks might
131          * sneak in and mess things up.  If we could believe the
132          * locking were honored, then we could also eliminate the
133          * chmod below after the rename.
134          */
135         fd = open(temp, FWRONLY|FCREATE|FEXLOCK|FNBLOCK, 0600);
136         if (fd < 0) {
...
139                         fprintf(stderr, "password file busy - try again.\n");
...
142                 exit(1);
143         }
...
145         if ((tf = fdopen(fd, "w")) == NULL) {
146                 fprintf(stderr, "passwd: fdopen failed?\n");
147                 exit(1);
148         }
149         /*
150          * Copy passwd to temp, replacing matching lines
151          * with new password.
152          */
153         while ((pwd = getpwent()) != NULL) {
...
165                 fprintf(tf,"%s:%s:%d:%d:%s:%s:%s\n",
166                         pwd->pw_name,
167                         pwd->pw_passwd,
168                         pwd->pw_uid,
169                         pwd->pw_gid,
170                         pwd->pw_gecos,
171                         pwd->pw_dir,
172                         pwd->pw_shell);
173         }
174         endpwent();
175         if (rename(temp, passwd) < 0) {
176                 fprintf(stderr, "passwd: "); perror("rename");
177                 unlink(temp);
178                 exit(1);
179         }
180         chmod(passwd, 0644);
181         fclose(tf);

ざっとこのコードを説明すると

  • 53~60行目 … ユーザー存在チェック、getpwent(3) で /etc/passwd を読み込んでいる
  • 135行目 … 一時ファイル作成、 open(2) + FCREATE(今のO_CREAT)で /etc/ptmp という決まった名前で作成
  • 153~174行目 … passwdファイルの内容をコピー、/etc/passwd の内容を再び getpwent(3) で読出しながらパスワードを変更して /etc/ptmp に書込み
  • 175行目 … 一時ファイルとpasswdファイルの置換、rename(2) で /etc/ptmp を /etc/passwd に上書きする

という流れ、ただこれ一時ファイルは /tmp に作られるわけでもないし一般ユーザ権限じゃ /etc にシンボリックリンクなんざ貼れないしどう攻撃すればいいのかさっぱり判らんよね。

なのでヒントを探しにもう一度 CVE-1999-1388 を読んでみると

  passwd in SunOS 4.1.x allows local users to overwrite arbitrary files via a symlink attack and the -F command line argument.

とある、ん? 4.1cBSD の passwd(1) にはそもそもオプションスイッチなんざ存在しないんですが…

  NAME
         passwd - change login password

  SYNOPSIS
         passwd [ name ]

そこで当時の SunOS 4.1.xのマニュアルで確認すると

  OPTIONS
  	-F filename
  		Treat filename as the password file

とあって passwd(1) には 惨OS 独自の拡張機能が施されていて この-Fオプションは /etc/passwd でなく任意のファイルを指定して編集が可能だったということ。 なんやこれ、chroot(2) の座敷牢に置く /etc/passwd の編集用にこんなヤバい機能つけたんですかねこれ…

このデンジャラスな機能のために passwd(1) が4.1cBSDから改造されてたんであれば以下のシナリオで /etc/passwd を上書き可能ちゅーことですな。

  • 書き込み権限のある場所で攻撃用のディレクトリを作成する
    $ pwd
    /tmp
    $ mkdir fake
    
  • ↑にrootのパスワードを空にした攻撃用のpasswdファイルを用意する
    $ whoami
    tnozaki
    $ cat > fake/passwd
    root::0:0:Charlie &:/:/bin/sh
    tnozaki::1000:1000:Takehiko NOZAKI:/home/tnozaki:/bin/csh
    ^D
    
  • 攻撃用のシンボリックリンクを貼る
    $ ln -s fake attack
    $ ls -all attack
    lrwxr-xr-x  1 tnozaki  tnozaki  3 May 10 10:00 attack -> fake
    
  • passwd(1)で攻撃用のpasswdファイルを↑のシンボリックリンク経由で編集を行う
    $ passwd -F attack/passwd
    New Passwd:
    
  • パスワード入力待ちの間にシンボリックリンクを /etc に変更する
    $ ln -s /etc attack
    $ ls -all
    lrwxr-xr-x  1 tnozaki  tnozaki  3 May 10 10:30 attack -> /etc
    

と恐ろしくも簡単な手順で攻撃可能だったのではないんですかねこれ…

上記の操作を実行すると

  • 53行目と153行目の getpwent(3) を拡張したパスワードファイル読込処理は、どちらも実体は /tmp/fake/passwd を読み込んでいる
  • 135行目の open(2) で作成される一時ファイルは /tmp/fake/ptmp のはずだけど、シンボリックリンクが張り替えられた事で /etc/ptmp になってしまった
  • 153~174行目で /etc/ptmp に書き込まれる内容は /tmp/fake/passwd である
  • 175行目の rename(2) は /etc/ptmp -> /etc/passwd への置換になる

となり、/etc/passwd を一般ユーザー権限でお好みの内容に置換えてしまえるって寸法。

そもそも passwd(1) が

  • /etc/passwd の編集には管理者権限が必要
  • しかし一般ユーザーでであっても自身のエントリーは passwd(1) コマンドで編集可能でなくてはならない
  • その為に passwd(1) の実行ファイルは setuid root されていて特権プロセスとして動作する

という仕様だからでもあるんだけど、この件に関しては 前回ちょっとだけ触れたケーパビリティーなどによる特権の分割も役に立たんのがアレ、なんせ権限の本丸だからな /etc/passwd は。

ちなみにソース考古学に必須である古い商用UNIXのドキュメントは The BITSAVERS.ORG Documents Libraryに紙をePub化したものがあって大変便利、いつもの Ancient UNIXだとソース公開されたものしかないからねぇ。

他にも Center for Computer Historyでお賽銭程度の有償で買えたりもするんだけど、SunOS 5.1のUser Manual買ったら半分しか無くてワイぶち切れですわ。

@次回

じゃあどうすればこの攻撃は避けられたかを書く予定、だがいつになるかは知らん(飽きた)。

*1:まぁWindowsも TxF捨てたし多少はね?

[音楽] Now, Now/AZ

新曲すこ、おっぱい眼鏡ことJess Abbott(Tancred)が脱退してもNow, Nowは変わらなくて安心したというかCacie Dalagerちゃん歌めっさ上手くなったな、アルバム「 Saved」買わないとなぁ。来日はまぁ諦めてます。

2018/05/21(Mon)

[NAS] IO-DATA HDL-Z4WS

去年くらいに退役させたWindows Storage Server 2008R2採用のNASである IO-DATA HDL-Z4WSについて、ひとつ思い出したことあるので書いておこう。

この機種ってQNAPのOEM製品でTS-459 Proとほぼ同一製品だというのは 導入時にも記事にしたんだけど、マシンスペックがAtom D510/2GBと非力過ぎてWindows Updateにも困るレベルだもんでUSBメモリ起動で OpenMediaVaultFreeNASでも動かせばええやんくらいに思ってたんだけど *1、どうせなら QTS(QNAPカスタマイズによる組込Linux)を動かそうとしてる人もいたのよね、5ch(旧2ch)の このスレ918とか。

ただこの報告にもあるようにQTSから起動するとHDD1~2が認識せず実質2ベイモデルとしてしか使えないという問題があるらしい、分解すると一部基板のシルクに「TS-459 WSS」の文字があるのでそもそもハードウェア仕様が異なるのが原因なんじゃないかなんて解析記事もヒットする。

でもこれそんな大層な問題じゃないと思うんよな、そもそも基板が違うったってフロントのアクセスランプ周りだけっぽいし。

何より自分がDebianベースの GPartedというLive USBから起動した時はちゃんとHDD1~4まで全部認識してATA Secure Eraseも問題なく完走したんだよね、ちょっと記憶があやふやだけど

というデバイス名で見えてて、さっきのレスにあるHDD1が/dev/sdivとして認識されるとかHDD2は見つからないとかいう問題はなかったんよね、ということでQTSの問題としか思えん。

最初は起動スクリプトかどこかに機種名とベイ数の対応表をハードコードで持ってて、「TS-459 WSS」の場合はそもそもQTSを動かすこと想定してないので2ベイモデルにフォールバックしてるのでは?と考えて、GPLなんだしソース読めばええやんという話で ダウンロードしてみたんだけど

$ grep -ri "TS459" .
./hdparm-6.1/hdparm.c:#elif defined(TS259) || defined(TS459) || defined(TS559) || defined(TS659) || defined(X86_SANDYBRIDGE) || defined(X86_CEDAVIEW) || defined(X86_EVANSPORT)
./hdparm-6.1/hdparm.c:#elif defined(TS259) || defined(TS459) || defined(TS559) || defined(TS659) || defined(X86_SANDYBRIDGE) || defined(X86_CEDAVIEW) || defined(X86_EVANSPORT)
./hdparm-6.1/hdparm.c:#elif defined(TS259) || defined(TS459) || defined(TS559) || defined(TS659) ||  defined(X86_SANDYBRIDGE) || defined(X86_CEDAVIEW) || defined(X86_EVANSPORT)
./hdparm-6.1/hdparm.c:#elif defined(TS259) || defined(TS459) || defined(TS559) || defined(TS659) ||  defined(X86_SANDYBRIDGE) || defined(X86_CEDAVIEW) || defined(X86_EVANSPORT)
./hdparm-6.1/Makefile:        MODEL_FLAG = -DTS459

のようにhdparm(ハードディスクのパラメータ取得/設定コマンド)のコードにQNAPでカスタマイズした機種依存のコードがあるにはあるんだけど、2~6ベイモデルのTS[2456]59で区別は無い上に、割愛するけどifdefの中身もキャッシュ制御と省電力関係のコードだけで無関係っぽいのよね。

これ以上原因調べるならUSB-DOM入手してQTS入れるかだけど結構お高いのでパス、普通のUSBメモリで代用できるならやらんでもないんだけどね。

ところでQNAPって最近はLinuxベースのQTSだけでなくFreeBSD + ZFSベースの QES(QNAP Enterprise Storage)なんて載せた機種も出してるのね、GPL vs CDDL問題で今更ながらFreeBSDの採用が増えるなんて案件があるんだなぁと。

*1:結局もろもろめんどくさくなってWindows Storage Serverのまま使い続けてしまった、MS Security EssentialがライセンスOKだったというのも大きい

2018/05/22(Tue)

[やきう] 完封負け

前節の巨人戦で筒香復活したようにみえたけど、どーせドームの温度が首のヘルニアによかったか痛み止め入れたかで、巨人も菅野はまだしも田口野上は弱点のインハイへの速い球無いから参考記録よな思ってたら、やっぱりハマスタ戻ってきたら全然ダメやな。

つーかメッセ菅野にまったく勝てないのもだけど、引退寸前の山井を去年ノーノーでリハビリし延命させ今年もしっかり完封されるし同じ相手に負け過ぎや、試合はじまる前から結果わかってほんとつまらん。

[セキュリティ] SpectreNG

SpectreNGだとさ、 CVE-2018-3640(Variant3a)はええとして CVE-2018-3639(Variant4)の方でまたさらに速度下がるってて大草原。

2018/05/23(Wed)

[音楽] 高橋幸宏/Tomorrow Never Knows

昔ミスチルがトゥモローネバーノウズという曲を出したことでオマージュ元のThe Beatlesの名曲が検索汚染されてしまい一部からは不興買ってたりするんだけど、今週になって今度はミスチル自身があの淫夢によって検索汚染されることになってしまったってのは因果が巡るってやつなんやな…チベット死者の書の輪廻転生や…

クッソ汚い導入やけど浄化するか、このThe Beatlesの方のTomorrow~のカバーはオエイシスとかケミブラとかいろいろあるけど、高橋幸宏のアルバム「 EGO」収録のやつがすき。

そいや最近気づいたんだけど、何でこの曲を選んだのかって高橋幸宏がRingo Starrの大ファンということだけでなく、鈴木慶一とのユニットThe Beatniks(これもThe Beatlesのもじりだ)で使われてる出口主義(Exitentialism)という造語は実存主義(Existentalism)のもじりなんだけど、この曲の最後のヴァースで繰り返される

Or play the game "Existence" to the end of the beginning

と対応してるんだなと、やっぱりこのアルバムって名曲↓も入ってたりで本当はThe Beatniksのアルバムとして出す予定だったんだな、レーベル移籍とかでアレやっただろうか。

ちなみにそのThe Beatniksは 新アルバムがちょうと出たとこだけど、シェーシェーシェーって新曲はこれTrio/DA-DA-DAのオマージュやねこれ。

この曲のピコピコ音は高野寛も愛した CASIO VL-TONE(VL-1)で、自分もこれがはじめて使ったシンセサイザーなのでとても懐かしい、 VSTi Pluginもあるのでどうぞ、Kontaktなら Calc-U-Synthに入ってるそうな。

ちなみにワイまだ実機持ってるぜー

[SNS] fav/likeのような機能は、他の誰かをムカつかせフレームを起しPVを増やすために再利用される可能性

あの機能自体が「ふぁぼ乞食」みたいな自己承認欲求拗らせマンの玩具でアレなんやけど、しばらく前からTwitterのfav/likeがいつの間にかフォロワーのTLに垂れ流されるようになり、大勢から反発されとんのにわざわざ新規にブクマを実装して矛先かわそうとしてるのってドス黒い理由がある気がするのよね。

同業のFacebookはかつて一部のユーザーを対象として「ニュースフィードを操作して情動感染の実験をした結果www」みたいな論文をドヤ顔で発表して 大炎上したけど、これTwitterも同じようなことをしれっとfav/likeを恣意的に流すことで実現しとるよねーとワイ疑ってんのよね(パラノイア)。

この「情動感染」って何かというと、わかりやすいところでは5ch(旧2ch)のゲハ板発祥の自作自演の対立煽りってのがそれ、ありとあらゆる話題を「信者 vs アンチ」の罵り合いに誘導すれば、顔真っ赤にしてスレに張り付くやつとそれ観にくる火事場の野次馬見物が増えてPV稼げるというやつ。

これはアフェリエイト収入目当ての2chまとめブログ(いわゆるアフィカス)という連中が手動でやっていたと噂されている、どこまで組織だってるかは不明だけど存在は自演バレなどの傍証は数多く残されている。

匿名掲示板みたいな場所だと話題によってスレが分散してるから手口としてはテンプレ改変しての手動コピペが主になるんだけど、SNSはありとあらゆる話題がタイムラインとして流れてくるので、それを自由に弄れる公式がこの対立煽りをやろうと思い立ったらもっと簡単かつ効果的そして全自動で可能という危険性は皆頭に置いておくべきよな。

たとえば「他の人はこれをfav/likeしてます」という体で、その実は煽るような内容のツイだけTLに流せば対立煽りがいっちょあがり。煽る内容についてはそれほど難しい機械学習(笑)とか必要なく、直近でblockしたアカウントと近い関係 or 同類のツイだけピックアップする程度ですら効果はある。

ほんとこういうアルゴリズムはブラックボックスにさせず開示義務とか課して透明性確保しないとアカンよなと思うんだが、インターネットはすでに広告に殺されたから無理やろうな。

2018/05/24(Thu)

[サッカー] イニエスタ、ヴィッセル神戸へ

Nintendo Switchでイニエスタサッカーが発売され売れ残ったカートリッジがエロゲに流用されるってやっぱり今年は平成ヒトケタなんじゃないですかねぇ…

鹿島のジーコになるかそれとも名古屋のリネカーになってしまうのか、やきうなら楽天AJかユーキリスやね。

2018/05/26(Sat)

[やきう] 大量得点

結局のところ上位打線が機能すれば勝てるんだよな、連敗中に下位打線ばかり問題にしてセカンド変えろキャッチャー変えろ騒いでた連中がいかに頭ハッピーセットかって話。

そんで先発石田の見慣れた初回失点そして援護のすぐ裏での吐き出しは、要するに下位打線は抑えられても上位には通用しとらんってことなので捕手云々の問題じゃないのよなアレ、大きくは燃えないから防御率詐欺しとるけど、決め球が無いPの宿命ではある。

にもかかわらず開幕から組んでた戸柱のリードを叩いてる連中多くて閉口したんだけど、嶺井高城と組んでもダメでさすがに頭ハッピーセットでも気づくかと思ったんだが、山本西森使えといってる奴おってほんまリード厨ってアレですわ。

とはいえノーコンPうまいことリードしてた鶴岡をFA補償で阪神に放出したことで、三嶋がボロボロになったりというのもあったけどな(しろめ)

その三嶋が今年は制球とか変化球とか捨てて速球で押すピッチングでルーキーイヤーのように三振とりまくってるのはほんと喜ばしいことである、というか体一回り大きくなって別人のようである。

2018/05/27(Sun)

[モタスポ] F1 第8戦 モナコGP 決勝

リカルドがシューイをモナコ大公に強要し激怒させて来年からモナコGPがカレンダーから消滅とかならおもしろいのに。

まぁ久しぶりに焼きパパイア観れたのでええわ。

2018/05/30(Wed)

Ulrich Shnauss/I Take Comfort In Your Ignorance (Tycho Remix)

EngeneersTangerine Dreamのメンバーであり、ソロでも活動してる Ulrich Shnaussの作品が好きで最近聴いてるんだけど、もうひとつワイの大好きな Tychoがリミックスに参加してる曲があるのを最近気づいたので光速で購入。

これCD出てなくてビニール盤かダウンロード版しかなくて気づかなかったんよな。

ダウンロード版はiTunesで買わずに、Warpレコード傘下の Bleepで買うのがええです、ハイレゾうんちゃらは詐欺商売ですがさすがにMP3/AACはクオリティがクソなので、CDクオリティのWAV/FLACがDRM無しで買えるのは非常にありがたいすね。

最近はアルバムでなくシングルやEPだとCD発売されず、ビニール盤とダウンロード版のみという地獄のような時代なのでやむなくダウンロード版を買うことがあるんだけど、iTunesで買うのはほんと最後の手段でアーティスト本人や契約レーベルから買えるかまず調べたほうがいい。

ほんとiTunesのシステムはアレで、あるアルバムがCD出てなくダウンロードだけなので諦めて買ったんだけど、半月後くらいに足りないオマケ(デジタルブックレット)を添付し忘れたようでファイルを追加しようなんだけど別アルバム扱いになってしまって、既存の購入者は購入済み表示にならずオマケもダウンロードできず、改めて全曲買い直さないとならん状態になってたりしてなぁ。これサポートに問い合わせたけどレーベル側の問題だから救済は無しだそうで。落丁で交換無しならデジタルの意味とは何よな、やっとれんわ。

ちなみにこのトラブルに遭ったのはこの曲入ったEP他、数枚ある。

ちなみにGhostly InternationalレーベルはさっきのBleepで扱ってる *1ので、iTunesなんかで買わなきゃ良かったですわ。

*1:あと自分とこでダウンロード販売もやってるけど、MP3だけだった記憶

2018/05/31(Thu)

[pkgsrc] オレオレ N6 だと lang/gcc8 がビルドできねぇ

理由は単純で POSIX:2008 Extended API Set Part2 の fstatat(2) なんかの syscall が未実装なのでビルドがコケる。

つーかこれは未実装だからというよりも gcc8 に含まれる libsanitizer がちゃんとこれらの実装があるかを configure で検出せずに

#if defined(__NetBSD__)
# define SANITIZER_NETBSD 1
#else
# define SANITIZER_NETBSD 0
#endif

とsanitizer_platform.h の中で OS 決め打ちのknobを定義して

uptr internal_stat(const char *path, void *buf) {
#if SANITIZER_FREEBSD || SANITIZER_NETBSD
  return internal_syscall(SYSCALL(fstatat), AT_FDCWD, (uptr)path,
                          (uptr)buf, 0);
#elif SANITIZER_USES_CANONICAL_LINUX_SYSCALLS
  return internal_syscall(SYSCALL(newfstatat), AT_FDCWD, (uptr)path,
                          (uptr)buf, 0);
#elif SANITIZER_LINUX_USES_64BIT_SYSCALLS
# if defined(__mips64)
  // For mips64, stat syscall fills buffer in the format of kernel_stat
  struct kernel_stat kbuf;
  int res = internal_syscall(SYSCALL(stat), path, &kbuf);
  kernel_stat_to_stat(&kbuf, (struct stat *)buf);
  return res;
# else
  return internal_syscall(SYSCALL(stat), (uptr)path, (uptr)buf);
# endif
#else
  struct stat64 buf64;
  int res = internal_syscall(SYSCALL(stat64), path, &buf64);
  stat64_to_stat(&buf64, (struct stat *)buf);
  return res;
#endif
}

と大量のifdefぶち込んでるのでクッソ醜いコードやんけ…控えめにいってICBM撃ちこみたい。

余談だけどもこの fstatat(2) などの syscall は TOCTTOU 対策の APIとして紹介されることが多いんだけど、 以前説明したような攻撃を防ぐような銀の弾丸ではないことに注意しよう。

以下のようなコードがあったとしよう。

static const char *path = "../unko.txt";
stuct st;
int fd;
char magic[8];

if (stat(path, &st, 0) == 0 && S_ISREG(st.st_mode)) {
	fd = open(path, O_RDWR);
	if (read(fd, magic, sizeof(magic)) < sizeof(magic))
		abort();
	...
}

上記のように stat(2) や open(2) に相対パスを引数として渡す必要がある場合、基点は getcwd(3) で得られるものと同じ「現在の作業ディレクトリ(Current Working Directory)」になるけども、この現在の作業ディレクトリってやつはプロセス単位の値なのでスレッドセーフではない、sys/proc.hと

209 struct proc {
...
222         struct cwdinfo  *p_cwdi;        /* :: cdir/rdir/cmask info */
...

sys/filedesc.hを参照のこと。

...
165 typedef struct cwdinfo {
166         struct vnode    *cwdi_cdir;     /* current directory */
167         struct vnode    *cwdi_rdir;     /* root directory */
168         struct vnode    *cwdi_edir;     /* emulation root (if known) */
169         krwlock_t       cwdi_lock;      /* lock on entire struct */
170         u_short         cwdi_cmask;     /* mask for file creation */
171         u_int           cwdi_refcnt;    /* reference count */
172 } cwdinfo_t;
...

よって stat(2) と open(2) の間に別スレッドで chdir(2) が呼ばれたりすると両者が扱うファイルは別物にすりかわって、予期しない動作となってしまう。

なもんでマルチスレッドアプリケーションの場合は stat(2) でなく fstatat(2)、open(2) でなく openat(2) を使うことで

static const char *path = "../unko.txt";
stuct st;
int curdir, fd;
char magic[8];

curdir = open(".");
if (curdir < 0)
	abort();
...
if (fstatat(curdir, path, &st, 0) == 0 && S_ISREG(st.st_mode)) {
	fd = openat(curdir, path, O_RDWR);
	if (read(fd, magic, sizeof(magic)) < sizeof(magic))
		abort();
	...
}

のようにして必ず相対パス解決の基点をファイル記述子として引数に渡してやることで、他のスレッドで chdir(2) 呼ばれても安全になる、つまりは MT-Safe の為の API なんよね。

つーか上のコード例程度であれば

static const char *path = "../unko.txt";
char *rpath;
stuct st;
int fd;
char magic[8];

rpath = realpath(path, NULL);
if (rpath == NULL)
	abort();
...
if (stat(rpath, &st, 0) == 0 && S_ISREG(st.st_mode)) {
	fd = open(rpath, O_RDWR);
	if (read(fd, magic, sizeof(magic)) < sizeof(magic))
		abort();
	...
}
free(rpath);

スレッド作る前にrealpath(3)で相対パスを排しておけばいいだけなので以下略、本当にこいつら必要だったんですかね…

そんで真に注意すべきなのはこれらのAPIはシンボリックリンク攻撃的なものは一切防げない、fstatat(2) と openat(2) が呼び出されるわずかな時間には相変わらず TOCTTOU の攻撃を仕掛けるチャンスが存在する。

だから顧客が本当に必要だったコードとは

static const char *path = "../unko.txt";
char *rpath;
stuct st;
int fd;
char magic[8];

rpath = realpath(path, NULL);
if (rpath == NULL)
	abort();
...
fd = open(rpath, O_RDWR);
if (fstat(fd, &st, 0) == 0 && S_ISREG(st.st_mode)) {
	if (read(fd, magic, sizeof(magic)) < sizeof(magic))
		abort();
	...
}
fclose(fd);
free(rpath);

みたいにまず先に open(2) しておいてからfstat(2)を使うべきなんだよなぁ。

なので fstatat(2) が必要になってる時点で逆にこのコードやばそう…と怪しんでしまうんだけども、この話はまた改めて書きたいと思ってるんだけどやる気がナッシング。