Chromebookの開発者モードへの遷移をちょっと追う

某ブログに触発されて、公開情報からChromiumOSの気になるところを追う軽い記事でも書こうかなという気持ちに急になったので書いていく。正確さも何も保証しないし、公開情報しか載っていないのであしからず。公開情報であることを示すために出典へのリンクをいっぱい張ります。

Chromebookには開発者モードというunsecureなモードがあって、これを使うと自作OSをブートさせたり色々できて楽しい。それに入るためには、リカバリモードに入ってCtrl-Dを押してEnterを押すという作業が通常必要なわけだが、これで何が起きるのかちょっと追いかけてみよう。

まず、ChromebookBIOSでもUEFIでもないファームウエアを採用している。(一部例外はある。)これはverified bootとかvbootとか呼ばれていて、このあたりにいい感じの図がある。情報が最新の状態に更新されているかは謎だが…(斜め上に視線を動かす)

あと、ここのページにファームウエアまわりの情報が載っていて、こんなかっこいい一文が書いてある。

The Chrome OS firmware is always verified as signed by Google

いいはなし。Root of Trustってやつだね。

さて、このページの下の方に、H2C (UEFI-based), U-boot, Coreboot, とChromebookで使われていた歴代ファームウエアの名前が並び、それに続いてdepthchargeってやつが新し目のChromebookだと使われているぞ!と書いてある。へえ〜!

そこからこんなスライドへもリンクが飛んでいるが、なんと2014年である。まあつまるところ、多分近頃のChromebookはみんなdepthchargeを使ってるんじゃないかな?(適当)

さて、そんなわけで、depthchargeのコードはここにあるわけだが、まずREADMEがない。なるほど…?

ちなみに"depthcharge chromiumos"でググる無機質なGitilesというシステムのページに飛ばされるが、ここは検索機能とかがないので、便利なChromium Code Searchを使おう!超便利だよ!(いつもお世話になっているので頭が上がらない)

さて本題に戻って、とりあえずdepthcharge配下で"recovery"とかの単語で調べると、src/platform/depthcharge/src/vboot/ui.hというファイルがヒットする。

/* Screens. */
enum ui_screen {
    /* Wait screen for EC sync and AUXFW sync */
    UI_SCREEN_FIRMWARE_SYNC             = 0x100,
    /* Broken screen */
    UI_SCREEN_RECOVERY_BROKEN           = 0x110,
    /* Advanced options */
    UI_SCREEN_ADVANCED_OPTIONS          = 0x120,
    /* Language selection screen */
    UI_SCREEN_LANGUAGE_SELECT           = 0x130,
    /* Debug info */
    UI_SCREEN_DEBUG_INFO                = 0x140,
    /* Firmware log */
    UI_SCREEN_FIRMWARE_LOG              = 0x150,
    /* First recovery screen to select recovering from disk or phone */
    UI_SCREEN_RECOVERY_SELECT           = 0x200,
    /* Invalid recovery media inserted */
    UI_SCREEN_RECOVERY_INVALID          = 0x201,
    /* Confirm transition to developer mode */
    UI_SCREEN_RECOVERY_TO_DEV           = 0x202,
    /* Recovery using phone */
    UI_SCREEN_RECOVERY_PHONE_STEP1          = 0x210,
    UI_SCREEN_RECOVERY_PHONE_STEP2          = 0x211,
    /* Recovery using disk */
    UI_SCREEN_RECOVERY_DISK_STEP1           = 0x220,
    UI_SCREEN_RECOVERY_DISK_STEP2           = 0x221,
    UI_SCREEN_RECOVERY_DISK_STEP3           = 0x222,
    /* Developer mode screen */
    UI_SCREEN_DEVELOPER_MODE            = 0x300,

https://source.chromium.org/chromiumos/chromiumos/codesearch/+/main:src/platform/depthcharge/src/vboot/ui.h;l=198-226;drc=3eda3d7bd3195f6d8ff59954e6b2967e42201bcb

UI_SCREEN_RECOVERY_TO_DEVとか、いかにもそれっぽい名前なので、今度はそれで検索すると、src/platform/depthcharge/src/vboot/ui.cというファイルが見つかる。まさに特定のキーを押したときに画面遷移するコードが書いてある!

 /* Manual recovery keyboard shortcuts */
    if (ui->key == UI_KEY_REC_TO_DEV ||
        (CONFIG(DETACHABLE) &&
         ui->key == UI_BUTTON_VOL_UP_DOWN_COMBO_PRESS)) {
        return ui_screen_change(ui, UI_SCREEN_RECOVERY_TO_DEV);
    }

https://source.chromium.org/chromiumos/chromiumos/codesearch/+/main:src/platform/depthcharge/src/vboot/ui.c;l=39-44;drc=fcfa6efb8f221877e771a7bc382267a3ebcdee48

ちなみにここに書いてあるとおり、キーボードが外れるタイプのデバイス(DETACHABLE)だと、音量キーの同時押しでdevmodeに入れることがわかる。(もちろんドキュメントに書いてあるよ!)

さて、じゃあこの画面の遷移した先を見に行こう。

src/platform/depthcharge/src/vboot/ui/screens.c

static vb2_error_t recovery_to_dev_init(struct ui_context *ui)
{
    if (ui->ctx->flags & VB2_CONTEXT_DEVELOPER_MODE) {
        /* Notify the user that they are already in dev mode */
        UI_WARN("Already in dev mode\n");
        return set_ui_error_and_go_back(
            ui, UI_ERROR_DEV_MODE_ALREADY_ENABLED);
    }

    if (!CONFIG(PHYSICAL_PRESENCE_KEYBOARD) &&
        ui_is_physical_presence_pressed()) {
        UI_INFO("Physical presence button stuck?\n");
        return ui_screen_back(ui);
    }


    if (CONFIG(PHYSICAL_PRESENCE_KEYBOARD)) {
        ui->state->selected_item = RECOVERY_TO_DEV_ITEM_CONFIRM;
    } else {
        /*
         * Disable "Confirm" button for other physical presence types.
         */
        UI_SET_BIT(ui->state->hidden_item_mask,
               RECOVERY_TO_DEV_ITEM_CONFIRM);
        ui->state->selected_item = RECOVERY_TO_DEV_ITEM_CANCEL;
    }

    ui->physical_presence_button_pressed = 0;

    return VB2_SUCCESS;
}

static vb2_error_t recovery_to_dev_finalize(struct ui_context *ui)
{
    UI_INFO("Physical presence confirmed!\n");

    /* Validity check, should never happen. */
    if (ui->state->screen->id != UI_SCREEN_RECOVERY_TO_DEV ||
        (ui->ctx->flags & VB2_CONTEXT_DEVELOPER_MODE) ||
        ui->ctx->boot_mode != VB2_BOOT_MODE_MANUAL_RECOVERY) {
        UI_ERROR("ERROR: Dev transition validity check failed\n");
        return VB2_SUCCESS;
    }

    UI_INFO("Enabling dev mode and rebooting...\n");

    if (vb2api_enable_developer_mode(ui->ctx) != VB2_SUCCESS) {
        UI_WARN("Failed to enable developer mode\n");
        return VB2_SUCCESS;
    }

    return VB2_REQUEST_REBOOT_EC_TO_RO;
}

https://source.chromium.org/chromiumos/chromiumos/codesearch/+/main:src/platform/depthcharge/src/vboot/ui/screens.c;l=799-851;drc=85f76f13f623d3e4e3a01224a381abf755f1c1bb

vb2api_enable_developer_mode()ってやつがあやしいね!そして、それを呼んだあとは再起動してるみたい。depthchargeの中にはtest用のmockしかなかったので、ひとつ上のsrc/platformで検索をかけると、vboot_referenceで実装が見つかった。

vb2_error_t vb2api_enable_developer_mode(struct vb2_context *ctx)
{
    if (ctx->boot_mode != VB2_BOOT_MODE_MANUAL_RECOVERY) {
        VB2_DEBUG("ERROR: Can only enable developer mode from manual "
              "recovery mode\n");
        return VB2_ERROR_API_ENABLE_DEV_NOT_ALLOWED;
    }

    uint32_t flags;

    VB2_DEBUG("Enabling developer mode...\n");

    flags = vb2_secdata_firmware_get(ctx, VB2_SECDATA_FIRMWARE_FLAGS);
    flags |= VB2_SECDATA_FIRMWARE_FLAG_DEV_MODE;
    vb2_secdata_firmware_set(ctx, VB2_SECDATA_FIRMWARE_FLAGS, flags);

    VB2_DEBUG("Mode change will take effect on next reboot\n");

    return VB2_SUCCESS;
}

https://source.chromium.org/chromiumos/chromiumos/codesearch/+/main:src/platform/vboot_reference/firmware/2lib/2misc.c;l=433-452;drc=def2f5af7ac03eac3a4a79795275f1cf6cd7f827

VB2_SECDATA_FIRMWARE_FLAG_DEV_MODE ってフラグがセットされるんだね〜

ということでこいつを調べると、フラグの定義も見つかった。

/* Flags for firmware space */
enum vb2_secdata_firmware_flags {
    /*
     * Last boot was developer mode.  TPM ownership is cleared when
     * transitioning to/from developer mode.  Set/cleared by
     * vb2_check_dev_switch().
     */
    VB2_SECDATA_FIRMWARE_FLAG_LAST_BOOT_DEVELOPER = (1 << 0),

    /*
     * Virtual developer mode switch is on.  Set/cleared by the
     * keyboard-controlled dev screens in recovery mode.  Cleared by
     * vb2_check_dev_switch().
     */
    VB2_SECDATA_FIRMWARE_FLAG_DEV_MODE = (1 << 1),
};

https://source.chromium.org/chromiumos/chromiumos/codesearch/+/main:src/platform/vboot_reference/firmware/2lib/include/2secdata.h;l=25-40;drc=6a33a0fca3e4f5cd9c3b3fd4ac0b5b8c7ffc018e

コメントいわく、最後の起動時の状態も記録しておいて、もしnormalからdevに遷移していたら、TPMのownershipをクリアするって書いてあるね!まあ要するに、normalのときに保存されたユーザーの秘密情報がもれないように、なんでもありのdevmodeに遷移するときはデータを消し飛ばすってことなんでしょう。(実際devmodeに移行するとなんかの消去が走っている画面になる。)

次回はその消去の画面のところを見に行きましょう!(オープンソース部分であることを祈りましょう…)

おやすみなさい!