2018-07-13

NPMのESLintのパッケージにマルウェアが混入された問題

Postmortem for Malicious Packages Published on July 12th, 2018 - ESLint - Pluggable JavaScript linter

https://github.com/eslint/eslint-scope/issues/39

要約

2018年7月12日に、攻撃者がESLintメンテナーのnpmアカウントを不正利用し、悪意あるコードが混入したeslint-scopeとeslint-config-eslintパッケージをnpmレジストリに公開した。インストール時に、悪意あるパッケージがダウンロードされ、pastebin.comからコードを実行し、このコードはユーザーの.npmrcファイルの中身を攻撃者に送信する。通常.npmrcファイルにはnpmでパッケージを公開する際のアクセストークンが含まれる。

悪意あるパッケージのバージョンはeslint-scope@3.7.2 並びに eslint-config-eslint@5.0.2であり、すでに両方共npmから非公開になっている。このパッケージが使っているpastebin.comのリンクもすでに取り下げられた。

npmは2018-07-12 12:30 UTC以前に発行されたすべてのアクセストークンをrevokeした。この結果、この攻撃により不正に取得されたすべてのアクセストークンは利用不可能になっているはずだ。

アカウントを不正利用されたメンテナーはnpmパスワードを複数の他のサイトに使いまわしており、かつnpmアカウントに2段階認証を有効にしていなかった。

我々、ESLintチームは今回の出来事について謝罪いたします。この失敗を他のメンテナーは他山の石としてnpm全体のセキュリティを高めることを願っています。

影響を受けたパッケージ

  • eslint-scope@3.7、このスコープ解析ライブラリは他の有名な複数のパッケージから依存されている。これには古いeslintと最新のbabel-eslintとwebpackが含まれる。
  • eslint-config-eslint@5.0、これはESLintチームによって内部的に使われている設定用のパッケージであって、よそではほとんど使われていない。

独自のnpmレジストリを運営している場合、これらのパッケージから悪意あるバージョンを非公開にすべきである。npmjs.comレジストリはすでに非公開にした。

攻撃手法

攻撃手法の詳細についてはhttps://gist.github.com/hzoo/51cb84afdc50b14bffa6c6dc49826b3eを参照。

推奨

今回の事例から、我々はnpmパッケージメンテナーとユーザーが今後取るべき推奨事項をいくつか提案する。

  • パッケージメンテナーとユーザーは同じパスワードを複数の違うサイトに使いまわすことをやめるべきである。1PasswordやLastPassのようなパスワードマネージャーは使いまわさなくても済むような利便性を提供してくれる
  • パッケージメンテナーは2段階認証を有効にすべきである使い方のドキュメント。Lernaを使っているのであれば、ここも参照
  • パッケージメンテナーはnpmの公開権限を持つ人間を精査、制限すべきである。
  • パッケージメンテナーはauto-merge dependency upgradesを提供するサービスの使用に慎重を期すべきである。
  • アプリケーション開発社はlockfile(package-lock.json、もしくはyarn.lock)を使い、新しいパッケージの自動インストールを差し止めるべきである。

時系列

  • 問題発生前:攻撃者はおそらくメンテナーがよそで使いまわしたメールとパスワードが流出しているのを発見し、これを使いメンテナーのnpmアカウントにログインした。
  • 2018年7月12日早朝:攻撃者はメンテナーのnpmアカウントで認証トークンを生成した。
  • 2018-07012 09:49 UTC: 攻撃者は生成された認証トークンを使い、eslint-config-eslint@5.0.2を公開した、これには悪意あるpostinstallスクリプトが含まれ、このスクリプトはローカルマシンの.npmrcの認証トークンの取得を試みる。
  • 2018-07-12 10:25 UTC: 攻撃者はeslint-config-eslint@5.0.2を非公開にした。
  • 2018-07-12 10:40 UTC: 攻撃者はeslint-scope@3.7.2を公開した。これには悪意あるpostinstallスクリプトが含まれる。
  • 2018-07-12 11:17 UTC: ユーザーがeslint/eslint-scope#39"を投稿、ESLintチームに問題を通知。
  • 2018-07-12 12:27 UTC: pastebin.comにある悪意あるコードが貼り付けられたリンクが取り下げられた。
  • 2018-07-12 12:37 UTC: ESLintメンテナーから連絡を受けたnpmチームはeslint-scope@3.7.2を非公開にした。
  • 2018-07-12 17:41 UTC: ESLintチームはeslint-scope@3.7.1のコードをeslint-scope@3.7.3として公開した。これによりキャッシュは新しいバージョンを使えるようになる。
  • 2018-07-12 18:42 UTC: npmは2018-07-12 12:30 UTC以前に生成されたすべてのアクセストークンをrevokeした。

リンク

2018-07-11

C++20の汎用エイリアス宣言の提案

P0945R0: p0945r0: Generalizing alias declarations

提案に誤りが有りすぎる。

C++のドラフトに入る見込みが高そうな提案に、汎用エイリアス宣言がある。

C++では、名前に別名をつけることがよく行われている。

型はtypedef指定子やエイリアス宣言によって別名を付けられる。

typedef int type ;
using type = int ;

関数は転送関数を書くことにより、実質別名を付けられる。

int f( int x ) ;

int g( int x )
{
    return f(x) ;
}

ただし、値は完璧に転送できないし、関数のアドレスも異なるものになってしまう。

変数はリファレンスで別名を付けられる。

int x = 0 ;
int & y = x ;

非staticデータメンバーはストレージを消費せずに別名をつけることができない

enumeratorはconstexpr inline変数によって別名を付けられる。

enum { value } ;

constexpr inline auto flag = value ;

ただしinline変数は名前空間スコープでしか使えない。

名前空間は名前空間エイリアスにより別名を付けられる。

namespace a { }

namespace b = a ;

型テンプレートはエイリアステンプレートで別名を定義できるが、デフォルトテンプレートパラメーターまで再現しなければならない。

template < typename T, typename Allocator = std::allocator<T> >
using vec = std::vector<T, Allocator> ;

ただし、この別名は別のテンプレートとして解釈されてしまう。

コンセプトは別のコンセプトを定義すれば別名を付けられる。

template < typename T >
concept newConcept = oldConcept<T> ;

名前の種類によって別名を宣言する文法が異なるし、単なる別名以上の意味を持つものもある。

この提案では、別名の宣言をエイリアス宣言に集約する。


using alias_name = name ;

型は今までどおりだが、その他

 型、今までどおり
using type = int ;

// 関数
int f(int) ;
using g = f ;

// 変数
int x = 0 ;
using y = x ;

// 非staticデータメンバー
template < typename Type, typename Value >
struct map_node : std::pair<Type, Value>
{
    using std::pair<Type, Value>::pair ;

    using key = first ;
    using value = second ;
} ;

[]{
    map_node< int, std::string> n( 123, "123") ;
    n.key ;
    n.value ;
} ;

// enum

enum { value } ;

using flag = value ;

// 名前空間

namespace a ;
using b = a ;


// 型テンプレート

using vec = std::vector ;

vec<int> v ;

// コンセプト

using newConcept = oldConcept ;

だいぶすっきりする。とくに非staticデータメンバーがよい。

2018-07-10

C++に提案されている静的例外

C++に静的例外が提案されている。

[PDF] P0709R1: Zero-overhead deterministic exceptions: Throwing values

例外はそのパフォーマンスへの影響が懸念され、一部のC++プロジェクトではコンパイラーオプションによって例外自体が無効化されていた。

これは由々しき事態だ。というのも、例外は標準C++の機能の一部であり、標準ライブラリは例外の存在を前提にして設計されている。例外が無効化されているということは、それはもはやC++ではない。C++風の別言語を使っていることになる。

なぜ例外は忌避されるのか。例外のパフォーマンスが非決定的だからだ。例外の実装はスタックからの確保ではなくヒープからの動的なメモリ確保が必要で、例外のキャッチにはRTTIによる型情報の比較が必要だ。

ifとgotoによるエラー処理のパフォーマンス特性は決定的に見積もることができるが、動的メモリ確保のパフォーマンス特性は決定的に見積もることができない。非決定的なパフォーマンス特性を持つ処理は、例えば数ミリ秒以内に必ず処理を終えなければならないような状況で使うことはできない。

この提案では、従来の例外を動的例外とし、新たに静的例外を追加して、例外のパフォーマンスを決定的にする。

まず、静的例外では例外としてthrowできる型が制約を受ける。特別に用意した何らかのstd::error型を投げる。このerror型は内部的には整数型で、デストラクターを実行する必要がなく、サイズは最大でもポインター2つ程度を想定している。標準はこのような型をrelocatableな型として規定する。

この標準のerror型はstd::error_codeを拡張したようなクラスで、様々な一般的にエラーに用いられるカテゴリーわけされた整数を返すことができる。このerror型はC++の標準の例外型は数値で表現できるようになっているので、静的例外と動的例外は標準の範囲であれば相互に変換可能になっている。

静的例外を扱う関数、静的例外関数を追加する。静的例外関数は静的例外指定によって明示的に指定する必要がある。提案では仮に、thorwsキーワードを用いる文法を提案している。


string f() throws
{
    if ( is_error() )
        throw error::something ;
    else
        return "hoge"s ;
}

静的例外関数はあたかもnoexcept(fase)が指定されたかのように振る舞う。したがってnothrow系のtraitsもそのように振る舞う。

静的例外関数が例外を外に投げる場合、例外を従来のスタックアンワインディングを伴う例外の仕組みを使わず、戻り値で返す。つまり静的例外関数はunion { R ; E ; } + boolのような型を内部的に戻り値として返す。ようするにexpected<T,E>のような型と同等の機能を提供する型を返す。

静的例外というのはerror型の値に変換できる値を投げるthrow式のことだ。それ以外のthrow式は動的例外となる。

// 静的例外
throw error::foobar ;
// 動的例外
throw "error"s ;

静的例外関数のなかで静的例外がthrowされ、その関数のローカルに対応する例外ハンドラーがある場合、該当する例外ハンドラーにgotoでとんだものと同じ挙動をする。

 string f() throws
{
    try {
        // ローカルのcatchにgotoで飛ぶのと同じ
        throw error::something ;
    } catch ( error e )
    {
        // ここにgotoで飛ぶのと同じ
    }
}

静的例外関数のローカルに該当する例外ハンドラーがない場合、errorは戻り値として返される。そのため、上の関数は実際には、union { string ; error ; }という型とどちらのunionメンバーが有効かを示すbool値を返したものとみなされる。これは従来のreturnと同じ仕組みで実装できるので、パフォーマンス特性も決定的になる。

静的例外関数が別の静的例外関数を呼び出し、静的例外によるerror型が返った場合は、その場で静的例外がthrowされたものとみなして処理する。

string f() throws 
{
    // 内部的にはerrorがreturnされる
    throw error::something ;
}

string g() throws
{
    try { return f() ; }
    catch( error e )
    {
        // ここにgotoで飛ぶ
    }
}

静的例外関数の中で従来の動的例外が投げられた場合、それが直接throw式で投げられたにせよ、呼び出した関数を通じて間接的に投げられたにせよ、直ちにその場でキャッチされ、適切な例外ハンドラーが選ばれる。

string f() ; // 非静的例外関数
string g() throws ; // 静的例外関数

string h() throw
{
    try {
        auto a = f() ;
        auto b = g() ;
        return a + b ;
    }
    // 静的例外
    catch( error e )
    {
    }
    // 動的例外
    // 従来の非決定的なパフォーマンス特性を持つ
    catch( std::bad_alloc e )
    {
    }
}

もし、静的例外関数から従来の動的例外が投げられ、関数の中に該当する例外ハンドラーがない場合、例外の型がerrorであれば静的例外としてreturnされ、それ以外であればstd::exception_ptrで束縛されてreturnされる。

string fail()
{
    throw "always error"s ;
}

string f() throws
{
    return fail() ;
}

この関数fは、string型をstd::exception_ptrで束縛してreturnする。

std::exception_ptrはC++11から追加された例外のオブジェクトを束縛できる機能だ。もう7年前の大昔に追加された機能なので読者は当然知っているはずだ。

呼び出した静的例外関数がstd::exception_ptrをreturnした場合、その場でただちに例外オブジェクトが取り出され、例外処理が行われる。もし例外オブジェクトがerror型の場合は静的例外が、そうでない場合は動的例外として処理される。

一部のC++の標準例外は、error型に変換される。例えばstd::bad_allocはerror型である(名前は仮のものだが)std::errc::ENOMEMに変換される。

非静的例外関数が静的例外関数を呼び出して例外を受け取った場合、もしerrorに対応するstd::exception型(たとえばerrc::ENOMEMからstd::bad_alloc)があるならば型を変換して動的例外がthrowされる。std::exception_ptrの中身がerror型の場合は取り出してerror型がthrowされたものとして処理される。それ以外の例外は動的例外がthrowされる。

string f() throws
{
    // 動的メモリ確保に失敗するコード
    // return std::errc::ENOMEM ; と同じ
    string s( std::numeric_limits<std::size_t>::max(), 'x') ; 
    return s ;
}

string g()
{
    // throw std::bad_alloc; と同じ
    return f() ;
}

この提案は従来の動的例外と組み合わせて使えるパフォーマンス特性が決定的な、つまりifとgotoを使うのと全く変わらないエラー処理を、例外の文法で実現する、静的例外を提案するものだ。これによりゼロオーバーヘッドの原則を満たした例外が扱えるようになる。

興味深いので入ってほしい。例外はすべてのC++で有効化されるべきだ。例外の使えないC++はC++風の別言語なので、利用者が分断されてしまう。 

2018-07-06

江添ボドゲ会@7月15日

以下の要領で7月15日に自宅でボドゲ会を開催します。

江添ボドゲ会@7月15日 - connpass

2018-07-05

プロフェッショナルIPv6の執筆経緯が興味深い

「プロフェッショナルIPv6」が出版されるそうだ。

すごいIPv6本を無料配布!:Geekなぺーじ

この本の執筆経緯が面白い。クラウドファンディングで金を集めている。

すごい技術書を一緒に作ろう。あきみち+ラムダノート『プロフェッショナルIPv6』 | クラウドファンディング - Makuake(マクアケ)

クラウドファンディングでは結果的に400万円ほど集まったそうだ。これはラムダノートと著者の連名のクラウドファンディングなので、著者の総取りというわけでもないだろうが、それにしても400万円は現代の技術書としては異例だ。

技術書とカネの話をしようと思う。私自身、技術書を出版してカネを得た経験がある。

技術書一冊の相場は数千円だ。売上一冊あたりの著者の収入は数百円だ。では著者が数百万円を稼ぐためには何冊売ればいいのだろうか。1万冊だ。問題は技術書で1万冊も売れる本は稀だということだ。1万冊売れる本というのは、技術書と言うよりはド素人に技術の仕組みをやんわりと教える本だ。詳細な解説をすればするほど、技術書は売れなくなっていく。千冊も売れればいいほうだろう。結果として、参考書を執筆して著者が得られるカネというのは数十万円だ。これでは経費を差し引くと給与所得者が確定申告に必要な雑所得の20万円すら下回る程度の利益しか得られない。そもそも技術書を執筆するためのまともなPCは数十万円するので、数年に渡って分割して経費にしなければならず、一台PCを買うだけで毎年本を出版しても数年は雑所得が確定申告が必要なレベルに達しない。

詳細な技術書を書くためには数年の時間がかかる。百万円台の収入では割に合わない。数年の執筆期間を維持するためには一千万円台の収入が必要だが、それには10万冊規模の売上が必要になる。日本で10万冊売れた技術書というのは、プログラマーたるもの読んだことがなければモグリのそしりを免れないような伝説的な本になるだろう。

ちなみに、100万冊売れた技術書は日本の全プログラマーが必携必読の書であり、このブログを読む読者は全員、手の届く範囲にその本を置いているような本になるはずだ。

さらに桁を上げるとどうなるのか。ありえないことだ。1000万冊売れる技術書などというものは存在するはずがない。それでもあえてそのような状況を発生させる条件を考えるとするならば、1000万冊売れた技術書の著者は新興宗教の開祖であり国内に何百万人もの信者を抱え、サイバークライムを救済されシリコンヘブンに行くためには免罪符として自著を購入しなければならない教義を説いているはずだ。

それにしても考えてみれば規模が小さい。現代の商業的な一般の書籍の流通に乗り、一般的な書店に並ぶ技術書の大半は千冊売れる程度なのだ。もはやコミケ以下だ。これでは技術書の執筆者は技術書を執筆するだけでは食べていけない。その結果、いま技術書を書いて生計を立てているような執筆者はいない。かの結城浩ですら、今は何をやっているかというと数学ラノベを書いている。

昔はこうではなかった。現代の感覚ではにわかに信じられないことであるが、昔は技術書を執筆するだけで生計を立てているプロの執筆者がいたと聞いている。一体どうしたらそんなことが可能になるのか。本の値段は何十年も変わっていない。むしろ最近のほうが安くなっている。一冊あたりの著者の印税も最近は安くなっているが、桁違いというほどではない。つまり昔は大抵の技術書が1万冊単位で売れていたということだ。事実、そうであったらしい。

昔は技術書が多く売れていた理由は、技術書が相対的に安かったからだ。今はインターネットの通信費用が限りないほど安価になっている。もはや日本では水と電気とインターネットは無料だと言ってもいい。紙の書籍は相対的に高い。しかし、昔は違った。通信費用は青天井に高かった。インターネットに接続するというのは、ISPの提供する基地局にモデムを介して電話をかけるということだった。そしてISPの基地局は都合よく同一市内の20km以内の場所に存在してくれたりはしない。帯域は本当に狭かった。当時の最後の時代の最高のモデムが56Kbpsだ。そもそも56Kbpsのカタログスペックがフルに出たりなどしない。たったの1MBをダウンロードするために何分もかかる、電話代は数百円かかるだろう。それを考えると、数千円で買える技術書が相対的に安くて売れるのは当然だ。

通信費が高いために、インターネット上にはそれほど情報がなかった事情もある。

現代ではインターネット上に情報が豊富にある上、最新の情報はインターネット上にしかない。本の執筆には時間がかかり、物理的に印刷して書店に並ぶには更に時間がかかる。しかも、今の技術の一次情報はすべて英語だ。すると、著者が英語を読んだ上で日本語で書くというオーバーヘッドもある。紙の本は出版された時点ですでに時代遅れなのだ。

さて、表題の本に戻ろう。今回クラウドファンディングで400万円を集めているわけだが、これはまだ低い。もう一つ桁が上がらなければ執筆に数年かかるような技術書は出せない。ただ、今回クラウドファンディングをしたことで圧倒的な宣伝効果を得たはずで、金銭以外の利益はあったはずだ。

不思議な時代だ。カネを払って宣伝をするのではなく、カネをもらって宣伝をするとは。結局、市場が小さすぎるのが悪い。

技術書の未来はどうなるのだろうか。私は今の傾向が続けば、もはや技術書などというものは滅びると思っている。そうなればプログラマーは皆日本語を捨てて余計なオーバーヘッドが排除されるので、長期的にはいいはずなのだが。

2018-07-04

C++17をすでに現場で使っているというキャディ株式会社に話を聞いてきた

CTOが「日本のC++のトップ人材の過半数が所属するイカれた会社にする」という宣言をした会社がある。なんとも壮大な話だ。C++プログラマーの業種は多岐にわたっているので、文字通りに考えると、そのような会社は自動車や旅客機の製造業であり、防衛庁の入札に参加する受注業者であり、OSや独自のプロセッサーを開発するためC++コンパイラー開発者も雇い、さらにはゲームもブラウザーも検索エンジンもクラウドホスティングもと挙げ続ければきりがないほど多方面に展開する大企業である。おそらくすでに名の知れた有名なIT系の大企業をほとんど買収すればそのような状態にはなるのではないかと思うが、金がいくらあっても足りない。

それはともかく、すでに現場でC++17を使っているという。

C++17は2017年に出たばかりの規格で、まだGCCもClangもコア言語はともかくライブラリーまでは完全に実装し終えてない状況だ。そのような状況で今C++17を使えるということは、最新の安定版のGCCやClangを本番環境で使える会社ということだ。果たして何をしているのか。

というわけで、その会社、「キャディ株式会社」に話を聞きに行った。

https://caddi.jp/: 板金加工なら【キャディ株式会社】―即日見積、5日納品、全国配送

Webサイトを見ると、板金加工をする会社だという。これだけではまだC++を使う理由がわからない。

話を聞いてみるとこうだ。

小規模な板金加工の受注生産というのは、小規模ないわゆる町工場が行っている。これまで板金加工をするには、町工場と直接交渉する必要があった。この顧客と町工場の間の交渉には、これまでほとんど技術革新がなかった。キャディはこの顧客と町工場の間に交渉に技術革新をもたらす、いわば仲介業か一次請けのような役割を果たす。

顧客は加工したいモデルデータをキャディのWebサイトからアップロードする。キャディはモデルデータを処理し、加工に必要な費用を見積もって表示する。この見積もりは一瞬で行われる。顧客が発注するとキャディは提携先の町工場に加工を依頼し、完成品を顧客に引き渡す。

顧客の送信したモデルデータから加工費用を一瞬で見積もるために、モデルデータの処理が必要だ。この処理にC++を使っている。

なるほど、C++を使う理由はわかった。ではどうやってこの比較的早い段階にC++17を使うことができるのか。その理由は簡単だった。規模の小さい新興企業だからだ。

現時点で従業員10人超、プログラマーが5人。そのような小規模な開発だからこそ可能になるのだろう。

大企業であればプログラマーとは別に専門のインフラ部署があり、インフラ屋はプログラマーとは少し違う目標を持っている。システムの安定性だ。新しすぎるソフトウェアは問題を引き起こす可能性がある。既存のソフトウェアに問題があるとしても、既知のものであり十分に情報があるので対処可能であるが、新しいソフトウェアの新しい問題は情報も少なく対処も難しい。この結果、インフラ屋はソフトウェアのアップデートに対して保守的になる。特にGNU/Linuxであればとても重要なC++コンパイラーであるGCCにはとても慎重になる。なぜならばGCCは他のほとんどのソフトウェアをコンパイルする重要なソフトウェアなのだから、GCCに問題があればシステムの全てに問題があることになる。

その結果、RHELのようなC++コンパイラーのアップデートが信じられないほどに保守的で時代遅れのディストロが使われる。

プログラマーが数人ですべてをやるような場合、この問題はない。

また、既存のコードが存在しないのも大きいのだろう。既存のコードが存在する場合、新しいコンパイラーによって不具合が修正され、その不具合に依存していたコードが壊れることがあるので、なかなかコンパイラーのバージョンを上げられない問題がある。

C++17をすでに現場で使っている話を聞くと、なんと私が過去に最新のClangを使っていて遭遇した問題に、同じく遭遇していた。

Clangは一時期、glibcのxlocale.hに依存していたことがある。これは非標準のglibcの独自ヘッダーでかなり昔からdeprecated扱いであり、最近削除された。

ディストロのパッケージにある安定版のClangを使いたい場合、私は空のxlocale.hを用意していた。パッケージ管理されたClangのヘッダーファイルを手動で書き換えるよりマシだ。

最新のClangでは修正されている。

Clangはvirtualデストラクターのある基本クラスをvirtual private派生で間接的に持っていた場合、デストラクターのアクセス指定を正しく判断できないregressionがある。Clang 3.3までは正しい挙動だったのだが、3.4から壊れてしまった。

30916 – If a class has indirect private virtual base with non-trivial public destructor, a class cannot access virtual base's destructor.

もう一年以上前にバグは報告してみたが、まだ修正されていないどころか何の反応もない。

こうして考えてみると、最新のC++コンパイラーには不具合も多い。既存の膨大なコードを修正するコストはかなり高い。常に最新のC++コンパイラーを使うのも茨の道だ。しかし、古いコンパイラーを使うということはこれ以上に莫大な既知の規格違反の不具合に対処する不思議なコードを書かなければならなくなるわけで茨の道であることに変わりはない。

ところで、このキャディ株式会社であるが、C++のプログラマーを随時募集しているらしい。応募方法はウェブサイトに記載されているメールアドレスに連絡してほしいとのことだ。

https://caddi.jp/: 板金加工なら【キャディ株式会社】―即日見積、5日納品、全国配送