VxWorksの脆弱性「URGENT11」のテクニカルホワイトペーパーを読む①

はじめに

7月末にVxWorks脆弱性発見が公開されました。

armis.com

日本語関連記事

VxWorksは組込み機器では非常に有名なRTOSで、20億以上のデバイスに搭載されています。 今回の脆弱性URGENT11で影響を受けるデバイスは2億個以上であると報告されています。

脆弱性を報告したARMISが公開している上のページでは、医療機器やルータで任意コードを実行するエクスプロイトデモ動画が掲載されています。

URGENT11では11個の脆弱性が報告されています。Wind Riverセキュリティアドバイザリによると、その中の3つは、CVEのレーティングが9.8と非常に脅威度が高いものとなっています。

URGENT11 テクニカルホワイトペーパー

本記事では、ARMISが公開している脆弱性のテクニカルホワイトペーパーを読み、自分なりにまとめてみます。 今回は、レーティングが9.8のもののうち、リモート (LAN内) から任意コード実行が可能なCVE-2019-12256Stack overflow in the parsing of IPv4 packets’ IP optionsに関する部分を読んでみます。

ホワイトペーパーに掲載されているコードスニペットでは一部解析できない部分があり、コードスニペットの解析では、一部推測が混じっています。

最終的には、「C言語で安全なコード書くの大変!」というお話になります。 ZenやRustのような配列 (またはスライス) が配列要素数情報を持っている言語であれば、(少なくとも)DoSまでは脅威度が下げられたように見えます。

URGETN11のホワイトペーパーではスタックオーバーフローと記載されていますが、スタックバッファオーバーフローなのではないかと疑っています。記事内では元のホワイトペーパーのままスタックオーバーフローとしています。

URGETN11

過去に発見されたTCP/IPスタックの脆弱性と同様のものが、ソースコードがクローズドなRTOSにもないかどうか、を研究しています。 研究にあたっては、ダウンロード可能なファームウェア (デバッグ情報付きのELF) を逆コンパイルしている、とあります。

概要

複数のSRR (Source Record Route) オプションを含む不正なIPパケットを送信すると、攻撃者が意図的にスタックオーバーフローを起こすことができ、任意コードが実行できます。これは適切な長さチェックを行わずに、SRRオプションをスタック上に確保したバッファにコピーすることに起因しています。

バックグラウンド情報

SRR (Source Record Route)

ja.wikipedia.org

ソース・ルーティングとは、ネットワーク通信において、データの送信者が送信先のみでなく中継地点をも指定する経路制御方式のことである。

通ってきたルート情報をオプション内に記録していきます。オプションヘッダが3バイトあり、その後ろに、通ってきたルートのIPv4アドレスが記録されます。

記録できるルート情報は最大9件です。IPv4のオプションフィールドが (固定部分を除き) 最大で40バイトなので、オプションヘッダ (3バイト) + ルート情報 (4バイト×9 = 36バイト) まで、ということなのでしょう。

ICMPエラーパケット

IPパケットを処理している間にエラー状態になった場合 (不正なIPパケットを受け取った場合) 、ICMPエラーパケットを返信します。この場合、不正なIPパケットとは、送信先に到達できないパケットである不正なオプションフィールドを含んでいる、などが挙げられます。

ICMPエラーパケットには、不正なIPパケットのコピーが含まれる場合が多いです。

はい、嫌な予感がしますね!

脆弱性

1つのIPパケットに複数のSRRオプションが含まれている状態は、エラーとして検出されます。しかし、脆弱性のあるVxWorksの不正IPパケット検出ロジックでは、そのエラーを検出する前に別のエラーを検出した場合、複数のSRRオプションが含まれていることを認識しないまま、不正IPパケットをコピーしてICMPエラーパケットを作成します。

ICMPエラーパケットとして確保されているオプションフィールドは、スタックに確保されている40バイトです。この領域に対して、最大で40バイトの大きさになるSRRオプションを複数回コピーしてしまい、スタックオーバーフローが発生します。

コード

ファームウェアのバイナリから逆コンパイルしたコードスニペットがホワイトペーパー内に掲載されています。

不正なIPパケットを受け取った場合、下のipnet_icmp4_send関数からICMPエラーパケットを送信します。

int ipnet_icmp4_send(Ipnet_icmp_param *icmp_param, Ip_bool is_igmp)
{
    Ipnet_icmp_param *icmp_param;
    Ipcom_pkt *failing_pkt;
    struct Ipnet_copyopts_param options_to_copy;
    // スタックに確保された40バイトの配列
    struct Ipnet_ip4_sock_opts opts;
    // ...
    // 問題のオプションをコピーする関数を呼び出す
    ipnet_icmp4_copyopts(icmp_param, &options_to_copy, &opts, &ip4_info);
    // ...
}

Ipnet_ip4_sock_opts構造体の定義が掲載されていないので詳細は不明ですが、ホワイトペーパー内では、これは40バイトの配列である、と解説されています。

int ipnet_icmp4_copyopts(Ipnet_icmp_param *icmp_param,
                         struct Ipnet_copyopts_param *copyopts_param,
                         struct Ipnet_ip4_sock_opts *opts, void *ip4_info)
{
// ...
    while ( 1 ) {
        current_opt = ipnet_ip4_get_ip_opt_next( /* ... */ );
        // ...
            if ( opt_type == 0x83 || opt_type == 0x89 ) {
                // IPオプションがSRR (LSRRもしくはSSRR)
                srr_ptr_offset = 39;
                srr_opt = (srr_opt_t *)&opts->opts[opts->len];
                // ポインタオフセットは最大で39バイト目までしか指さないようにしているが、
                // このオフセットが現在のオプション内で有効であるかどうか、は検証されていない
                if ( (int)current_opt[2] <= 39 )
                    srr_ptr_offset = current_opt[2];
                offset_to_current_route_entry = srr_ptr_offset - 5;
                // ...
                // オプション内に記録されているIPアドレスを逆順に1つずつコピーする。
                while ( offset_to_current_route_entry > 0 ) {
                    memcpy((char *)srr_opt + srr_opt->length, current_route_entry, 4);
                    current_route_entry -= 4;
                    offset_to_current_route_entry -= 4;
                    srr_opt->length += 4;
                }
                // 最新 (自身) のルート情報を追加する
                memcpy((char *)srr_opt + srr_opt->length, &icmp_param->to, 4);
                srr_opt->length += 4;
                total_opts_len = opts->len + srr_opt->length;
            }
        }
    }
...
}

SRRオプションは次のデータ構造になっており、lengthはオプションのバイト数で、pointerroute dataのオフセットです (オプション開始位置から数えるので、4から始まります) 。pointer (コード中のsrr_ptr_offset) はlength以下でなければならないはずですが、上記コードではそれがチェックされていません。

Loose Source and Record Route
+--------+--------+--------+---------//--------+
|    0x83| length | pointer| route data |
+--------+--------+--------+---------//--------+

脆弱性をつくには、次のようなIPオプションフィールドを送信します。

type length pointer type length pointer
0x83 3 0x27 (39) 0x83 3 0x27 (39)

これでルート情報が欠落しているLSRRオプションが2連続で続くことになります。実際にはルート情報はありませんが、pointer0x27 (39)を指しているので、9番目のルート情報が格納されている位置から逆順に、ルート情報をコピーしようとします

このとき、コピー先はmemcpy((char *)srr_opt + srr_opt->length, current_route_entry, 4);から、srr_optです。srr_optは、40バイトの配列であるoptsどこかを指すポインタです。opts->lenを更新するコードが掲載されていないのですが、一番外側のwhileループが回るごとにオプションの長さ分加算されていると推測できます。

srr_opt = (srr_opt_t *)&opts->opts[opts->len];

上述の脆弱性をつくオプションフィールドをwhile文で1つずつ処理し、2つ目のオプションを処理する際には、opts->len340になっていると考えられます (オプションフィールドのlengthで更新していれば3、コピーしたオプションフィールドの長さで更新していれば40) 。そこを起点に、40バイトしか確保していない領域に、さらに最大で40バイトのコピーが発生します。

ここでコピーされる内容は、オプションフィールドに続く任意のデータです

ということで、上位に戻って、ipnet_icmp4_sendoptsを突き抜けてデータを書き込まれ、スタック上のリターンアドレスが、書き込まれた任意コードの先頭アドレスに書き変われば攻撃成功、なはずです。

int ipnet_icmp4_send(Ipnet_icmp_param *icmp_param, Ip_bool is_igmp)
{
    Ipnet_icmp_param *icmp_param;
    Ipcom_pkt *failing_pkt;
    struct Ipnet_copyopts_param options_to_copy;
    // スタックに確保された40バイトの配列
    struct Ipnet_ip4_sock_opts opts;

新しいプログラミング言語なら?

一番の直接的な問題点は、pointerオフセットが有効なlengthの範囲内であるかどうか検証していないこと、でしょう。ここはロジックレベルの実装ミスであり、プログラミング言語レベルでは防ぎようがありません。

int ipnet_icmp4_copyopts( /* ... */ )
{
// ...
                srr_ptr_offset = 39;
                srr_opt = (srr_opt_t *)&opts->opts[opts->len];
                // ポインタオフセットは最大で39バイト目までしか指さないようにしているが、
                // このオフセットが現在のオプション内であるかどうか、は検証されていない
                if ( (int)current_opt[2] <= 39 )
                    srr_ptr_offset = current_opt[2];

最終防衛線は、optsが確保しているメモリの範囲外アクセスを検出することです。

// ...
                srr_opt = (srr_opt_t *)&opts->opts[opts->len];
// ...
                    memcpy((char *)srr_opt + srr_opt->length, current_route_entry, 4);
// ...

ZenやRustのような言語であれば、opts->optssrr_opt配列要素数を型の一部として持つ配列 (へのポインタ) 、もしくは、スライスになります。このような言語であればopts->opts[opts->len]で範囲外アクセスを行った場合や、スライスのコピー操作により範囲外アクセスを防ぐことが可能です。

どちらの言語でもunsafeC言語のmemcpyと同等のコピー関数がありますが、そのような関数を使うと、もちろんC言語と同じ結果になります。

次のコードは、かなり問題を単純化した例をZenで書いたものです。

const std = @import("std");

fn copy_opts(opts: *[]u8) void {
    const length: usize = 38;
    // `opts`の38番目から42番目の要素を指すスライスを取得して、書き換えようとする
    var entry = opts.*[length..length + 4];
    std.mem.copy(u8, entry, [_]u8{ 1, 2, 3, 4 });
}

pub fn main() void {
    var opts: [40]u8 = [_]u8{0} ** 40;
    copy_opts(&opts[0..]);
}

このコードは、パニックで停止します。copy_opts関数の引数optsが40個の要素しか持たないことがわかっているため、それを超える範囲のスライスを作ろうとすると、実行時にパニックが発生します。

index out of bounds
main.zen:5:23: 0x2250b6 in copy_opts (main)
    var entry = opts.*[length..length + 4];
                      ^

お次はRustで書いたものです。

fn copy_opts(opts: &mut [u8]) {
    let length: usize = 38;
    // `opts`の38番目から42番目の要素を指すスライスを取得して、書き換えようとする
    let entry = &mut opts[length..length+4];
    entry.copy_from_slice(&[1, 2, 3, 4]);
}

fn main() {
    let mut opts: [u8; 40] = [0; 40];
    copy_opts(&mut opts);
}

このコードも、パニックで停止します。

thread 'main' panicked at 'index 42 out of range for slice of length 40', src/libcore/slice/mod.rs:2555:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

パニック発生時にパニックを捕捉してハンドリングすることも可能です。

マルウェアを送り込まれた上で動作し続けることに比べると、システムが定義されたパニック状態に陥る方が、かなり被害が軽減できるでしょう。 このような最終防衛線がプログラミング言語レベルで用意されていることが、いかに大切かわかりますね!

技書博出展レポート〜初めての同人誌執筆で組込みRustの本を頒布しました〜

はじめに

記憶の新しいうちに、経緯などをまとめておきます。 自分用メモの側面が強いですが、今後初めて同人誌を頒布される方の参考になると嬉しいです。

f:id:tomo-wait-for-it-yuki:20190721132625p:plain
表紙

BOOTHで物理本と電子版を販売しているので、もし良ければお買い求め下さい。 (物理本は倉庫への搬送作業は完了しており、入荷待ちです)

sabizen.booth.pm

経緯

4月下旬、@hidemi_ishihara さんから出展のお誘いがありました。

組込みRustで書くことに決めて、今まで翻訳した組込みRustのドキュメントや、自分でこれまでやってきたことをクックブックとしてまとめることにしました。 クックブックにしたのは理由があり、急に執筆ができなくなっても読める内容になっている形式で執筆を進めたかったからです。 第二子の出産予定日が6月初旬だったため、この判断は良かったと思います。

性格的にギリギリにやるのは性分ではないこともあり、手持ちで書籍化できそうなものが、組込みRustかZephyrしかなかった、というのもありますが。 (Zephyrはそこまで思い入れないですからねぇ…)

執筆の進行

gitの履歴を見てみると、4/29にレポジトリを作っていました。 2週間後の5月中盤には、頒布した内容の半分は書けています。 この頃は、平日は1時間前後、休日は2〜3時間執筆していました。

ここまでは順調でしたが、まさかの第二子が3週間早く産まれてきてしまう、というハプニングに見舞われます! ガクッと執筆ペースが落ちて、残り半分の内容を埋めるのに6週間ほどかかっています。 この頃は、平日30分時間が取れれば良い方で、休日も2時間執筆できれば万々歳でした…。

7月に入り、組版作業に入ることを決めました。 この時点で、まだ書きたかった内容を全て切り捨てました。

初めてなので、どのくらい製本すれば良いかわかりませんでした。 とりあえずtwitterランドの住人に聞いたろ、ということで、聞いてみると、100イイねついたので、100冊刷ることにしました。

製本をどこに頼もうか調べていたところ、@hidemi_ishihara さんから、いつもポプルスさんで印刷している、という情報を得ました。 調べる時間がもったいないので、「じゃあ、そこで!」ということにして、早速アカウント作って見積もりと製本予約をしました。

ということで、組版作業を都合2週間ほどやっていました。 こんなに時間がかかったのは少し理由があって、mdbookというRust製のドキュメントビルダーで原稿を執筆していました。 mdbookには未完成品ですがEPUB形式で出力する機能があります。 mdbookから出力されたEPUB形式の原稿を、calibreという電子書籍エディタで編集していました。

慣れないCSSをいじったり、なぜかPDF出力するときにコードブロックの強調が消えてしまうバグと不毛な争いをしていました。 ということで、2週間前に組版を始めたのも、良い判断でした。

表紙作成もこの辺りの期間にやりました。 割と面倒で、時間かかりました。

7/12には入稿を済ませて、一段落つきました。

当日まで

不安しかねぇ!

というのも、初めての同人誌執筆(厳密にはD論という名の同人誌製本していますが)で会場直接搬入なので、実物が読めたものになっているかどうか、わかりません!

当初より今回の本は、組込みRustの知名度向上のため、価格を安くしてばら撒く作戦でした。 さらにmdbookは静的なページを作るツールなので、HTML版も合わせて配れば、安いし誰も怒らないでしょ!みたいな開き直りをすることで、心の安寧を保ちました。

その裏で、名刺を自炊したり、ダウンロードカード作ったりしていました。

後、twitterやブログで必死の宣伝活動していました。 どこかの記事で、技書博は集客1000人を目指している、と聞いて、来場者の10%もこんなニッチな本を買うわけがない!という焦りがありましたね。

値段は500円にしたので、とりあえず500円玉を30枚、お釣りとして用意しました。

当日

とにかく本のできを見て、一安心しました。 これなら500円でも怒られはしないでしょ!というクオリティになっていました。 少し上下左右の余白取りすぎた気がしますが、文字が詰まっている圧迫感も感じないので、悪くない気がします。 読者の皆様からの感想をお待ちしております。

@hidemi_ishihara さんに導かれるまま、見本誌にカバーかけたり、スペースの準備をしました。 カッターナイフも何度かお借りしました。カッターナイフ、意外と要るで?

午前中は、全然売れなかったです! 11時〜12時の間の売上は3冊でした。 内心すごい焦燥感に駆られていました。

イベント自体は、ゆっくりスペースを回れて、著者とお話しする余裕が十分にあるので、良い感じだなぁ、と思いました。 物が売れない焦燥感を除けば!

13時以降、徐々に売上が伸びていき、14時〜15時くらいの間に20冊近く販売できました。 最終的には、53冊頒布しました。

一般入場者が640名ほどとのことなので、このニッチなジャンルで53冊はだいぶ頑張った方ではないでしょうか笑 ゆっくり見れたり、話した結果ご購入下さった方もいらっしゃったので、購入する側としても満足度が高かったのかもしれません。

中には、「Rustはやったことないのだけど、気になるし、安いから買います(意訳)」という方や「Rust勉強してからまた来ます」、と言って下さった方も複数名いらっしゃったので、狙いは良かったと思います。

頒布時間終了後、40冊はBOOTHさんに入庫することにしました。 スーツケース持ってきておけば、持って帰って技術書典7で頒布できたなぁ、と思いましたが、後の祭りですね。 荷物軽くするために、リュック1つで行ったのが間違いでした…。

技術書典7では、また50冊くらい刷ることにします。

お金の話

100冊製本して、約37,500円でした。 ばらまきたいので、1冊500円で頒布することにしました。

現状、BOOTHでの売上も含めて、なんとか損益分岐点に到達しました! お買上げ、ありがとうございます! Boostまでして下さる方もいらっしゃって、非常にありがたいことです。

今回は、赤字にさえならなければ勝ちなので、満足です!

今後について

HTML版は、適宜更新していこうと考えています。 時間不足でバッサリ切ってしまった部分が残っているので、そちらの加筆も行う予定です。 組版はぼちぼち手間がかかるので、PDF版および紙版の更新は余裕があれば、やります。

読者の皆様からフィードバックがあれば、加筆修正する大きなモチベーションになるため、フィードバックをお待ちしております。

7/27(土) 技書博で組込み/ベアメタルRustクックブックを販売します!

はじめに

宣伝です!

来週開催される技術書同人誌博覧会にて、組込み/ベアメタルRustクックブックを販売します。 A-9 AQUAXISさんのブースにご一緒させて頂きます。 ブース主の石原ひでみさんはFPGAの薄い本とMarkdown組版の本を、みつきんさんはNuttxの本を頒布されます。

f:id:tomo-wait-for-it-yuki:20190721132625p:plain
表紙

販売情報

価格は、500円です。 PDF版とHTML版がデフォルトで含まれます。 当日の会場にてお買い上げいただくと、先着で100名様に、紙媒体をお渡しします。 1冊のご購入につき、紙媒体を1冊まで、お渡しします。

当日お越し頂けなかった方にも、後日何らかの形でPDF版とHTML版が入手できる手段を用意します。

目次は、次の通りで、表紙込で78ページです。

  • はじめに
  • 環境構築
  • ベアメタルテクニック
    • no_std
    • panic
    • print!マクロ
    • リンカ
    • アセンブリ
    • メモリアロケータ
    • entryポイント
  • ツール
  • ライブラリ / フレームワーク
    • heapless
    • no_stdクレート
    • svd2rust
    • RTFM
    • Tock
    • testing
  • FFI
  • 組込みLinux
    • ビルド/テスト
    • Yocto
  • あとがき

正直なところ、HTML版が一番見やすいので、HTML版に500円払う気持ちで購入して頂けると嬉しいです。

ところで

100イイねついたから、100部刷ったので、買って下さいね!(切実)

コネクトフリー求人情報[2019/7]

はじめに

コネクトフリー株式会社ではエンジニアを募集しています。 会社のホームページが用意できるまでの間、こちらに求人情報を掲載します。

コネクトフリー株式会社は、レガシーなメモリ寄りのコンピュータシステムを刷新し、新しいCPU寄りの社会インフラ構築の先駆けになります。 「低レイヤから世界を変えたい」、「新しい低レイヤをベースにこれまでにない高レイヤなシステムを構築したい」、 「良い仕事をして良い給料をもらいたい」、という方を職種を問わず募集しています。

コンピュータ・サイエンスやプログラミングが好きなエンジニアが集まって、面白いことをしましょう!

募集職種例

コネクトフリーでは、下記職種に関わらず、世界を変えたいエンジニアを広く募集しております。 職種ごとに主な業務内容について掲載します。 主なとしている理由は、記述した内容に関わらず、面白いと思える仕事を自ら切り拓くことを期待しているためです。 技術選定や技術習得も含め、状況に応じて柔軟に動けることを期待しています。

提示年収はレンジで示していますが、可能な限り最高額で雇用したいと考えています。

  • 正社員
  • 年収600 - 1000万円
  • 京都で継続して稼働可能な方
    • リモート勤務は業務内容に応じて相談可能

一覧です。


バックエンド

第二インターネットEVER/IPなど、コネクトフリーのビジネスを支えるバックエンドエンジニアを募集しています。 これまでとは違う仕組みのインターネット基盤を用いたシステムの運用から開発まで、多様な業務に関わります。

主な業務内容

  • コネクトフリーの第二インターネットEVER/IPを活用したバックエンドシステムの開発、運用

フロントエンド

コネクトフリーの斬新なプロダクトをユーザーに提供するWebアプリケーションを開発します。

主な業務内容

  • Webアプリケーションの設計、開発、試験

コンパイラ

コネクトフリーが開発しているZen言語を開発するエンジニアを募集しています。 LLVMをバックエンドとするシステムプログラミング言語の開発に従事できます。

主な業務内容

  • Zenコンパイラの開発
  • Zen標準ライブラリの開発
  • Zen言語エコシステムの開発

セキュリティ

コネクトフリーの目指すセキュアな社会インフラ構築のため、セキュリティを専門とする立場から他のエンジニアと協力してシステムを構築します。

主な業務内容


組込み

IoTデバイスに搭載するセキュアなファームウェアや制御ソフトウェアを開発します。 C/C++以外のプログラミング言語を用いた組込み開発が行えます。

主な業務内容

  • Zen言語による組込みシステムの開発

OSレイヤ

新しい社会インフラのためのOSレイヤ (Hypervisor / OS / VMなど) を開発します。

主な業務内容

  • Zen言語によるOSレイヤの開発

ハードウェア

NET BOYを始めとするIoT向けのハードウェア開発を行います。

主な業務内容

  • マイコン基板の企画、設計、開発
  • 基板の量産開発

その他、コンピュータ・サイエンスが好きで、こんなことがしたい!という意気込みがある方は、ぜひご連絡下さい。 カジュアルな形式での事業説明も承ります。

ご連絡は、下記のいずれかにお願いいたします。

email: tn@connectfree.co.jp  
twitter: @LDScell

担当窓口: 中林

このエントリはコネクトフリー株式会社の業務の一環として書かれています。

関連記事リンク

tomo-wait-for-it-yuki.hatenablog.com

tomo-wait-for-it-yuki.hatenablog.com

LLVM IRファイルを読み込む

はじめに

ファイルに出力されたLLVM IRをC++で読み込んで遊んでみます。

環境

$ clang++ --version
clang version 8.0.1-svn360950-1~exp1~20190517004233.70 (branches/release_80)
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
$ llvm-config --cxxflags --ldflags --system-libs --libs core
-I/usr/lib/llvm-8/include -std=c++11  -fno-exceptions -D_GNU_SOURCE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS
-L/usr/lib/llvm-8/lib 
-lLLVM-8

LLVM IRファイルの読み込み

アプリケーションの引数にLLVM IRファイルパスを指定する想定です。

#include <llvm/IR/LLVMContext.h>
#include <llvm/IR/Module.h>
#include <llvm/IRReader/IRReader.h>
#include <llvm/Support/SourceMgr.h>

int main(int argc, char** argv) {
    if (argc < 2) {
        llvm::errs() << "Expected an argument - IR file name\n";
        exit(1);
    }

    llvm::LLVMContext Context;
    llvm::SMDiagnostic Err;
    auto module = llvm::parseIRFile(argv[1], Err, Context);

    if (!module) {
        Err.print(argv[0], llvm::errs());
        return 1;
    }

    return 0;
}

llvm::parseIRFileに、ファイルパスを与えるだけで、llvm::Module (のunique_ptr) を得ることができます。

下のコマンドでLLVM IRファイルが読み込めます。

clang++ main.cpp `llvm-config --cxxflags --ldflags --system-libs --libs core` -o ir_reader
./ir_reader main.ll

少し内部を覗く

main.llにはmain関数があり、いくつか命令があります。 そのオペコードを表示してみます。

次のようなコードで実現できました。

    auto func = module->getFunction("main");
    std::cout << "function name: " << func->getName().data() << std::endl;
    for (auto &bb: *func) {
        auto i = 0;
        for (auto &instr: bb) {
            std::cout << "%" << i << ": " << instr.getOpcodeName() << std::endl;
            i++;
        }
    }
$ ./ir_reader main.ll 
function name: main
%0: alloca
%1: bitcast
%2: call
%3: getelementptr
%4: load
%5: call
%6: ret

お手軽ですね!

Rust × 組込みで前代未聞のInterfaceオフ会レポート

はじめに

昨日、2019年6月17日、巣鴨CQ出版社セミナールームにおいて記念すべき組込みRustのオフ会が開催されました! 今回のオフ会は、雑誌掲載前にオフ会を開催する、という前代未聞のオフ会、とのことでした。

inteface-meet-up.connpass.com

非常に盛り上がったオフ会になったので、そのレポートです。

プレゼン

さらに、LTとして2つの発表がありました。

組込みRustのすゝめ

まず、私から導入のお話をさせていただきました。

speakerdeck.com

最初に会場内でアンケートを取ったところ、

  1. まだRustをやったことがない、が半分ほど
  2. 入門はした、はまさの0人
  3. 組込み以外でRustをやっている、が2割弱
  4. 組込みRustをやっている、が3割弱

といった感じでした。 知らない人か、プロしかいない!という両極端な結果が印象的でした。

導入と言いつつ、所有権システムの説明を少し入れており、ここの部分はRust知らない方々にどの程度理解して頂けたか、が気になっています。

Rustでチョット気軽にセンサドライバ開発

@ryochack さんからホストPC上で組込みセンサドライバ開発を行う発表です。

speakerdeck.com

「いつまで僕らはC/C++を使い続けなければならないのか…」

全くその通りです!

発表内容は、下のデバイスを使って、PC上で直接Rustのセンサドライバを開発し、マイコンに持っていけるようにしよう!というものでした。

akizukidenshi.com

MPSSEのOSS実装、libmpsseからbindgenを使って、Rustバインディングを生成しています。 その時のラッパ作成の苦労話が、涙なしでは語れません。

デモされていたもののレポジトリは、こちらです。

github.com

Rustで始める自作組込みOS

@garasubo さんから、組込みOSを自作したお話しです。

speakerdeck.com

RustでOSを作る際の良かった話や、苦労話が生々しい発表でした! ライフタイムやテストフレームワークなど、良かった点もありますが、データ構造の実装が難しい、クレートが不足している、という課題もあります。

自作OSのレポジトリは、こちらです。

github.com

Nuttxでlibstdを動かす

杉野さんから、POSIX likeな組込みOSであるNuttxでlibstdを動かした発表です。 今のところ、資料公開はされていません。

茨の道をひたすら突き進むような発表を前に、参加者一同、「これはツラすぎないか…」みたいな空気になっていましたが、ある程度動くようになっていて、圧巻の内容でした!

Rustの安全性や利便性を最大限享受するためにlibstdを使いたい、というのは組込みRustやっている人が共通で持っている望みだと思います。 リンカのバグを踏んだり、OS側にAPIが不足していたり、OS側とRust側とで構造体のメンバが違ったり、と数々の難関をくぐり抜け、動いた先は--。

M5 StackをRustで動かすまで

@ciniml さんから、ESP32上でRustを動かすようにするまでに試したことの発表です。

www.slideshare.net

Rust (というかLLVM) は公式にXtensaに対応していないため、LLVMのforkを使ってrustcのXtensa対応を行われていました。 ESP-IDF側のmalloc/freeをGlobalAllocでラップして、libcoreの機能を使えるようにしていました。

@ryochackさんと同様に、bindgenを使用されており、その苦労話もありました。

embedded_graphicsクレートを使って、画像を描画することまでできていて、次はWi-Fiを動かしたい、とのことです!

docs.rs

Rustで書いたファームウェアが乗った自作キーボードのデモ

@KOBA789 さんからキーボード自作のインターン向けに作成したRustファームウェア搭載の自作キーボードについてデモがありました。

会社での無茶ぶり?から産まれたようです。 ステートマシン実装時、ステートが移るときにデータを引き継ぐところに苦労された、ということでした。

懇親会

組込みRustをやっている者同士のぶっちゃけトークが楽しかったです。 身代わりパターンめっちゃ使うよね〜、とかunsafe使いたくないんだけど、やりたいことができなくてツラい、などなど。 組込みRustでまだまだ不足している部分や、課題もありますが、やらないといつまで経っても使えるようにならないから、やる!という認識が共有されたのも、個人的には非常に嬉しかったです。

まだRustを触っていない方々から、実際のところどーなの?的な質問にも正直な回答をさせていただきました。

告知

WebAssembly Micro RuntimeでRustアプリをマイコンで動かす!

はじめに

前回、もう一歩のところだったのですが、RAMが2MB搭載されていないと動かない状態でした。

tomo-wait-for-it-yuki.hatenablog.com

私はそんなマイコン持っていないため、今回は、256KB RAMが搭載されているマイコン (これも高性能品ですが) 上でRustアプリを動作させます。

問題点

データセグメントが0x100000番地に配置されていることが問題でした。

$ wasm-objdump -x target/wasm32-unknown-unknown/release/hello_wasm.wasm 
...
Data[1]:
 - segment[0] size=16 - init i32=1048576
  - 0100000: 4865 6c6c 6f20 6672 6f6d 2052 7573 7400  Hello from Rust.

リンカオプションによる解決

調べていると、次のissueに行き当たりました。

github.com

The large memory size here is because we default to asking LLD to allocate a 1MB stack for all Rust binaries. If you'd like to reduce that though you can pass -C link-arg=-zstack-size=16 to rustc, for an appropriate stack size for your application. That should allow you to shrink quite a bit!

LLDにスタックを1MB割り当てるようなデフォルトになっており、このサイズを減らせば良い、ということでした。

ということで、リンカにオプションを与えます。

|> .cargo/config

[target.wasm32-unknown-unknown]
rustflags = [
    "-C", "link-arg=-zstack-size=16"
]

ビルドします。

$ cargo build --target=wasm32-unknown-unknown --release
$ wasm-strip target/wasm32-unknown-unknown/release/hello_wasm.wasm

中身を確認してみましょう。

$ wasm-objdump -x target/wasm32-unknown-unknown/release/hello_wasm.wasm

hello_wasm.wasm:        file format wasm 0x1

Section Details:

Type[3]:
 - type[0] (i32, i32) -> i32
 - type[1] () -> nil
 - type[2] () -> i32
...
Code[2]:
 - func[1] size=2
 - func[2] size=16 <main>
Data[1]:
 - segment[0] size=16 - init i32=16
  - 0000010: 4865 6c6c 6f20 6672 6f6d 2052 7573 7400  Hello from Rust.

これでデータセグメントが0x10 (16) から置かれるようになりました。

QEMUでの動作確認

実機で試す前に、QEMUを使って、RAM 64KB搭載のCortex-M3をターゲットにして、動作確認します。 (無理矢理動かすために、WebAssembly Micro Runtimeのページサイズを4KBにしています)

$ ninja run
[0/1] To exit from QEMU enter: 'CTRL+a, x'[QEMU] CPU: cortex-m3
qemu-system-arm: warning: nic stellaris_enet.0 has no peer
***** Booting Zephyr OS zephyr-v1.14.0 *****
Hello from Rust

よし!

実機動作

手元に偶然あるnRF52840-DKで動かしてみます。 RAMも256KBあるので、なんとかなるはずです。

$ mkdir nrf52840 && cd $_
$ cmake -GNinja -DBOARD=nrf52840_pca10056 ..
$ ninja
[10/14] Linking C executable zephyr/zephyr_prebuilt.elf
Memory region         Used Size  Region Size  %age Used
           FLASH:       76436 B         1 MB      7.29%
            SRAM:      144328 B       256 KB     55.06%
        IDT_LIST:          56 B         2 KB      2.73%
[14/14] Linking C executable zephyr/zephyr.elf

ビルドはOKです。

minicomでUARTを受信できるようにして、ファームウェアを書き込みます。

ninja flash
Welcome to minicom 2.7.1

OPTIONS: I18n 
Compiled on Aug 13 2017, 15:25:34.
Port /dev/ttyUSB0, 20:35:14

Press CTRL-A Z for help on special keys

***** Booting Zephyr OS zephyr-v1.14.0 *****
Hello from Rust

OK! 無事動きました!

WebAssembly Micro Runtimeの方は、次の通りです。

|> src/main.c

// デフォルトの512 KBでは実機のRAM 256KBに入らないため
static char global_heap_buf[128 * 1024] = { 0 };

|> iwasm/runtime/vmcore-wasm/wasm.h

// WASMは1ページ64KBとのことなので4KBから元に戻しています
#define NumBytesPerPage 65536
#define NumBytesPerPageLog2 16