フォトシンス エンジニアブログ

株式会社Photosynth のテックブログです

組み込みの知見をAIレビュワーに継承させてみた

組み込みの知見をAIレビュワーに継承させてみた

〜ベテランエンジニアの暗黙知ADR化してAIレビューに活かす〜

この記事は Akerun Advent Calendar 2024 - Qiita の17日目の記事です。

こんにちは。 いとう です。ファームウェアやってます。今年は実際に取り掛かった仕事が機密伴う仕事ばっかりだったので記事化は諦め、今回はアドベントカレンダーのために一念発起して組み込み開発全体に役に立つことがないか考えて記事を書きました。

はじめに

組み込みファームウェア開発では、ハードウェアの制約や開発環境の特性など、さまざまな暗黙知が存在します。これらの知見は多くの場合、ベテランエンジニアの経験として属人化しており、チーム全体での共有が課題となっています。今回は、これらの暗黙知ADR(Architecture Decision Record)として文書化し、AIによるコードレビューに活用した取り組みについて考えてみました。

暗黙知の例と影響

メモリ制約による問題

組み込み開発では、一見問題ない修正が思わぬ事態を引き起こすことがあります。例えば、ある開発者が計算精度向上のために整数演算を浮動小数点演算に変更したところ、大きな問題が発生しました。

// Before: 整数演算による重力加速度の計算
// センサー出力を物理値(m/s^2)に変換
// 9.80665...を10倍した98で近似
int32_t acceleration = (raw_value * 98) >> 10;  // 98/1024で近似

// After: 浮動小数点演算に変更
float acceleration = raw_value * 9.80665f / 1024.0f;

このシンプルな変更により、ARM7TDMI(FPU非搭載)プロセッサ上でソフトウェア浮動小数点ライブラリが自動的にリンクされ、わずか数行の修正でROMサイズが4KB増加。リリース直前のタイミングでFlashROMの容量制限に引っかかるという冷や汗モノの事態となりました。

組込み開発の現場では、FPUが搭載されていないプロセッサ(Cortex-M0/M0+など)が今でも現役。浮動小数点演算をコードに入れただけで、バイナリサイズが数KB増える事態に。原因は「ソフトウェア浮動小数点ライブラリ」が自動的にリンクされるため。

さらに驚くべきことに、Cortex-M0には除算器すら搭載されていません。つまり、単純な割り算もソフトウェアで実装することになり、想像以上に重い処理になってしまうのです。そのため、上記のコードは以下のように書き換えるのが定石です:

// 最適化バージョン:シフト演算を活用
// 9.80665を1024倍した10042で近似
int32_t acceleration = (raw_value * 10042) >> 10;

「きれいなコード」を目指して浮動小数点演算を使用した若手エンジニアが、リリース直前に容量制限にひっかかり青ざめる—— そんな光景は、組み込み開発ではよくある話です。

これを防ぐには、コードレビューで「これってFPU載ってましたっけ?」の一言を投げかけることが重要かもしれません。では、このような知見をどのようにチームで共有し、AIによるコードレビューに活かしていけばよいのでしょうか?

割り込みコンテキスト

ある日、新人エンジニアが相談に来ました。「デバッグプリントを増やしたら、たまにシステムが落ちるんです...」

void interrupt_handler(void)
{
    process_sensor_data();
    // 問題のコード
    LOG_INF("Interrupt: processed sensor data. value=%d", sensor_value);
}

たまにしか発生しない問題のため長い時間がかかった調査の末、判明した恐ろしい真相。RTOSの標準ログ機能のバックエンドとして使用していたSegger RTTが、バッファが一定量溜まると内部でmutexを取得しようとしていたのです。

つまり...

  1. 割り込みハンドラからのログ出力
  2. バッファが溜まると内部でmutex取得
  3. 割り込みコンテキストからmutexは取得できない!
  4. assert発生 → リブート

しかも、デバッガを繋いでいると発生しない(ログ出力が間に合う)という厄介な代物でした。

割り込みハンドラは神聖な領域。そこでは以下が絶対に禁止です:

  • メモリの動的確保
  • 同期処理(mutex, semaphoreなど)
  • 時間のかかる処理全般

これ以来、私たちのチームには「割り込みハンドラでのログ出力を見たら質問する」という暗黙の文化が根付きました。でも、こんな暗黙知、どうやって継承していけばいいのでしょうか?

ADRによる知見の文書化

組み込み開発では、ベテランエンジニアの「あの時の不具合」という武勇伝のような経験が、実は重要な技術的知見だったりします。今回はそんな暗黙知ADR(Architecture Decision Record)として文書化してみることにしました。

ADRのフォーマット

フォーマットは https://github.com/moomoo-ya/LADR-template を使わせていただきました。

以下に主なADRを示します。ベテランエンジニアの方々なら、タイトルを見ただけで「あぁ、あるある」と思い当たる節があるのではないでしょうか。各ADRは折りたたんでありますので、気になる項目をクリックして詳細をご確認ください。

なお、これらのADRはこの記事のために作成した例示用のものです。実際の現場ではより詳細な内容や、プロジェクト固有の制約が加わることでしょう。

浮動小数点の使用禁止

adr-01-浮動小数点の使用禁止.md

# Architecture Decision Records: 浮動小数点の使用禁止

## Date - 日付

2024-12-14

## Context - 文脈

プロジェクトで使用している Cortex M0 プロセッサは浮動小数点演算ユニット(FPU)を搭載していない。このため、float や double などの浮動小数点型を使用すると、ソフトウェアエミュレーションによって処理が行われ、以下の問題が発生する:

- ROM サイズの増大
- 実行速度の低下

## Status - 採用状況

Accepted

## Decision - 決定事項

すべてのコードにおいて以下の型の使用を禁止する:

- float
- double
- long double

代わりに以下の手法を使用する:

1. 整数型による演算 (int, long など)
2. ビットシフトを用いたスケーリング
3. 参照テーブルによる近似計算
4. 整数による比例計算

## Consequences - 結果

### メリット

- ROM サイズの削減
- 処理速度の向上
- 予測可能な実行時間

### デメリット

- 固定小数点演算の実装が必要
- 計算精度の制限

除算演算のガイドライン

adr-02-除算演算のガイドライン.md

# Architecture Decision Records: 除算演算のガイドライン

## Date - 日付

2024-12-14

## Context - 文脈

Cortex-M0 プロセッサは除算命令を持たず、コンパイラにより複数の命令に展開される。これは以下の課題を引き起こす:

- 実行時間が長い
- 命令数が多くコードサイズが増加
- 実行時間が一定でない

一方で、除算が必要な計算は一定数存在する。

## Status - 採用状況

Accepted

## Decision - 決定事項

除算を使用する前に、以下の代替手段を検討する:

1. シフト演算での代替
   - 2のべき乗による除算はシフト演算で実装

2. 定数による乗算への変換
   - コンパイル時に計算可能な除数は逆数を乗算に変換

3. テーブル参照
   - 頻繁に使用される値は Look Up Table で代替

上記で対応できない場合のみ除算を使用可能とする。
除算を使用する場合は、使用理由をコメントとして記載する。

コメント例:
    ```c
    // 任意の値での除算が必要なため、
    // シフトや定数乗算での代替が不可能
    result = value / divisor;
    ```

## Consequences - 結果

### メリット

- 処理性能の向上
- コードサイズの削減
- 実行時間の予測性向上

### デメリット

- 実装の複雑化
- コードレビューでの確認項目増加

固定幅整数型の使用義務化

adr-03-固定幅整数型の使用義務化.md

# Architecture Decision Records: 固定幅整数型の使用義務化

## Date - 日付

2024-12-14

## Context - 文脈

プロジェクトでは PC 上でのシミュレータと組み込み機器の両方で動作するコードを扱う。プラットフォーム間で以下の問題が存在する:

- int や long のサイズがプラットフォームにより異なる
- plain charの符号の有無が処理系定義である
- シミュレータと実機で異なる動作をする可能性がある
- デバッグが困難になる

## Status - 採用状況

Accepted

## Decision - 決定事項

### 1. 数値型として使用する場合

以下の型の使用を禁止し、代わりに固定幅整数型を使用する:

禁止する型:
- int
- long
- short
- unsigned int
- unsigned long
- unsigned short
- plain char (数値として使用する場合)

使用する型:
- int8_t, uint8_t
- int16_t, uint16_t
- int32_t, uint32_t
- int64_t, uint64_t

### 2. 文字列処理の場合

文字列処理においては以下のルールに従う:

    ```c
    // OK: 文字列リテラルと文字列処理
    const char* str = "hello";
    char buffer[16];

    // NG: 数値処理としてのplain char
    char value = 65;  // 符号の解釈が環境依存

    // OK: 数値処理には固定幅整数型を使用
    uint8_t value = 65;
    ```

## Consequences - 結果

### メリット
- プラットフォーム間での一貫した動作
- コードの意図が明確
- デバッグの容易さ
- 文字列処理の標準ライブラリとの互換性維持

### デメリット
- 既存コードの修正が必要
- タイピング量の増加
- 用途による型の使い分けが必要

割り込みコンテキストでのメモリアロケーション禁止

adr-04-割り込みコンテキストでのメモリアロケーション禁止.md

# Architecture Decision Records: 割り込みコンテキストでのメモリアロケーション禁止

## Date - 日付

2024-12-14

## Context - 文脈

割り込みハンドラは予測不可能なタイミングで実行され、リアルタイム性と信頼性が要求される。以下の問題が懸念される:

- 動的メモリ確保はヒープロックによりデッドロックの可能性がある
- メモリ断片化によりアロケーション失敗の可能性がある
- スタック使用量の予測が困難になる
- システム全体の応答性が低下する

## Status - 採用状況

Accepted

## Decision - 決定事項

割り込みコンテキストにおいて以下の操作を禁止する:

1. 禁止される関数:
   - malloc()
   - free()
   - new
   - delete
   - その他動的メモリ確保を行う関数

2. 代替手段:
   - 静的メモリ確保
   - プール管理
   - メッセージキュー

## Consequences - 結果

### メリット
- デッドロックの防止
- メモリ使用量の予測性向上
- システムの信頼性向上
- 割り込み応答時間の安定化

### デメリット
- 実装の制約
- 静的メモリ管理の必要性

0除算の実行時チェック義務化

adr-05-0除算の実行時チェック義務化.md

# Architecture Decision Records: 0除算の実行時チェック義務化

## Date - 日付

2024-12-14

## Context - 文脈

組み込みシステムにおいて、0除算は以下の重大な問題を引き起こす可能性がある:

- 未定義動作によるシステムの予期せぬ振る舞い
- ハードウェア例外の発生
- システムのクラッシュやリセット
- デバッグの困難さ

特に製品の出荷後は、このような問題の修正が極めて困難となる。

## Status - 採用状況

Accepted

## Decision - 決定事項

すべての除算処理において、以下のルールを適用する:

1. 除算前の0チェックを必須とする:
    ```c
    // 必須の実装パターン
    if (denominator != 0) {
        result = numerator / denominator;
    } else {
        // エラー処理(状況によって異なる)
    }
    ```

2. 禁止されるパターン:
    ```c
    // 禁止:チェックなしの除算
    result = numerator / denominator;
    ```

3. コンパイル時に確定する定数による除算は除く
    ```c
    // OK: コンパイル時に確定する除算
    const int32_t FACTOR = 100;
    result = value / FACTOR;
    ```

## Consequences - 結果

### メリット
- 実行時の予期せぬ動作の防止
- システムの信頼性向上
- デバッグの容易さ
- 問題の早期発見

### デメリット
- コードサイズの増加
- 実行パフォーマンスへの影響
- コーディング量の増加

ISR命名規則と制約

adr-06-ISR命名規則と制約.md

# Architecture Decision Records: ISR命名規則と制約

## Date - 日付

2024-12-14

## Context - 文脈

割り込みハンドラ(ISR)は以下の特性を持つ:
- 非同期で実行される
- 高い優先度を持つ
- 他の処理を遮断する
- リアルタイム性が要求される

特にログ出力に関して、以下の重大な問題が判明している:
- Zephyr RTTログバックエンドにおいて、特定条件下でミューテックスによるブロッキング待ちが発生
- これによりISRコンテキストで例外が発生するリスクがある
- システム全体の信頼性に関わる問題となる

## Status - 採用状況

Accepted

## Decision - 決定事項

1. 命名規則:
    ```c
    // 必須:_isr サフィックス
    void timer0_handler_isr(void)
    void uart_rx_isr(void)
    ```

2. ISR内での禁止事項:

- ログ出力の禁止(RTTを含むすべてのログバックエンド)

    ```c
    // 禁止:ISR内でのログ出力
    void uart_rx_isr(void) {
        LOG_ERR("Error detected");  // NG: ミューテックスによるブロッキングのリスク
    }
    ```

- デバッグプリントの禁止
- 長時間の処理
- 動的メモリ確保
- ブロッキング処理

3. 推奨される実装パターン:
    ```c
    void timer0_handler_isr(void) {
        // 最小限の処理のみ
        flag_set(EVENT_TIMER);
        // タスクへの通知
        notify_task(TASK_ID_TIMER);
        // ログが必要な場合はタスクコンテキストで実行
    }
    ```

## Consequences - 結果

### メリット
- ISRの明確な識別
- リアルタイム性の確保
- コードレビューの容易さ
- 実行時間の予測性向上
- ミューテックスによる例外の防止
- システムの信頼性向上

### デメリット
- タスクとの連携実装が必要
- コーディングの制約増加
- デバッグ情報の取得に工夫が必要

再帰呼び出しの制限

adr-07-再帰呼び出しの制限.md

# Architecture Decision Records: 再帰呼び出しの制限

## Date - 日付

2024-12-14

## Context - 文脈

組み込みシステムにおいて、スタックには以下の制約がある:
- 物理メモリが限られている
- スタックサイズが少なめに設定されている
- スタックオーバーフローは致命的な問題を引き起こす
- 実行時のスタック使用量の予測が困難

再帰呼び出しは特に以下の問題を引き起こす可能性がある:
- 呼び出し深度によってスタック使用量が変動
- 最悪ケースの予測が困難
- スタックオーバーフローのリスク

## Status - 採用状況

Accepted

## Decision - 決定事項

1. 基本ルール:
- 再帰呼び出しの使用を原則として禁止
- 反復(ループ)による実装を推奨

2. 例外的な許可条件:
- スタック使用量の解析が完了している
- 最大呼び出し深度が明確
- 解析内容を詳細にコメントで記録

3. 許可される実装例:
    ```c
    /*
    * スタック使用量解析結果
    * アーキテクチャ: Cortex-M0+
    * コンパイラ: GCC 10.2.1
    * コンパイラオプション: -Os -g
    * 解析ツール: ARM Compiler 6.16
    * 最大呼び出し深度: 4
    * 1回あたりのスタック使用量: 16バイト
    * 最大スタック使用量: 64バイト
    * 解析日: 2024-12-14
    */
    int32_t factorial(int32_t n) {
        if (n <= 1) return 1;
        return n * factorial(n - 1);
    }
    ```

4. 禁止される実装例:
    ```c
    // NG: スタック解析なしの再帰
    void process_tree(node_t* node) {
        if (node == NULL) return;
        process_tree(node->left);
        process_tree(node->right);
    }
    ```

## Consequences - 結果

### メリット
- スタック使用量の予測性向上
- システムの信頼性向上
- 実行時の安全性確保
- メモリ使用量の明確化

### デメリット
- 一部のアルゴリズム実装が複雑化
- スタック解析の工数が必要
- コード可読性が低下する可能性

### 運用上の注意点
- スタック解析は環境が変わるたびに再実施
- コンパイラのバージョンアップ時は要再解析
- 最適化オプションの変更時は要再解析

ADRについて

ここまでいくつかのADRを例示しましたが、読者の皆さんの現場でも「あるある」な事例が思い浮かんだのではないでしょうか? マイコンの機種固有の問題や、開発環境の制約、プロジェクト特有のノウハウなど、組み込み開発には多くの暗黙知が存在します。

このようなADRをチームで作成・運用していく過程自体が技術継承となります。また、後からチームに参加したメンバーにとっても、「なぜそうしているのか」を理解する貴重な情報源となるでしょう。

さて、せっかく形式知化したこれらのナレッジ。人力でコードレビューに活用するのも良いのですが、これをAIに学習させることで、より効率的なコードレビューができないでしょうか?そう考えた私は、あるツールを試してみることにしました...

AIレビュワーの活用

今回は Anthropic が発表し、話題になっている MCP を使用してリポジトリ内に格納したADR文書をもとにAIにレビューしてもらえないか検討してみました。

プロジェクトの構成

project/
├── adr/                      # ADRを格納するディレクトリ
│   ├── adr-01-浮動小数点の使用禁止.md
│   ├── adr-02-除算演算のガイドライン.md
│   └── adr-03-固定幅整数型の使用義務化.md
│
├── main.c
└── README.md                # プロジェクトの説明

MCPを使ったレビュー

Model Context Protocol (MCP) は、AIモデルに対してコンテキストを提供するための標準化されたプロトコルです。今回はこのMCPを利用して、ADRの内容をAIに理解させ、コードレビューを自動化してみました。

MCPの実装は https://github.com/modelcontextprotocol/servers から入手可能です。今回は以下の2種類のサーバーを使用します:

  • Local File Server: ローカルファイルシステム上のADRファイルを読み込むために使用
  • Git Server: リポジトリの任意の時点での差分を取得するために使用(git_diffがリリース版には提供されていないため、最新ソースをクローンして使用しています)

設定ファイル

claude_desktop_config.json の設定例:

{
    "mcpServers": {
        "git": {
            "command": "/Users/yuito/.local/bin/uv",
            "args": [
                "--directory",
                "/Users/yuito/src/github.com/modelcontextprotocol/servers/src/git",
                "run",
                "mcp-server-git"
            ]
        },
        "filesystem": {
            "command": "/Users/yuito/.volta/bin/npx",
            "args": [
                "-y",
                "@modelcontextprotocol/server-filesystem",
                "/Users/yuito/"
            ]
        },
    }
}

(コマンドが絶対パスで指定しているのは volta が悪さをしているのか PATH が通っているのに環境でコマンドが見つからないことがあったためです。原因の追及はしていませんが、ひとまず絶対パスを指定することで動作するようになりました。)

プロンプト例

コードレビューをしてもらいます
/path/to/git/dir/adr ディレクトリに複数のADR (Architecture Decision Record) があります。それぞれのADRについて、内容を確認してください。
/path/to/git/dir/ ディレクトリで git_diff を使って targetは origin/main..origin/source_branch で差分を取得してください。
この変更点に対して ADR に違反していることがないかチェックしてください。明確にADRに記載されている禁止事項があったときだけ指摘してください。

レビュー例

今回はZephyrの基本的なLチカサンプルに対しての変更をレビューしてもらうこととしました。

main.c(クリックで展開)

/*
 * Copyright (c) 2016 Intel Corporation
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <stdio.h>
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>

/* 1000 msec = 1 sec */
#define SLEEP_TIME_MS   1000

/* The devicetree node identifier for the "led0" alias. */
#define LED0_NODE DT_ALIAS(led0)

/*
 * A build error on this line means your board is unsupported.
 * See the sample documentation for information on how to fix this.
 */
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);

int main(void)
{
    int ret;
    bool led_state = true;

    if (!gpio_is_ready_dt(&led)) {
        return 0;
    }

    ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
    if (ret < 0) {
        return 0;
    }

    while (1) {
        ret = gpio_pin_toggle_dt(&led);
        if (ret < 0) {
            return 0;
        }

        led_state = !led_state;
        printf("LED state: %s\n", led_state ? "ON" : "OFF");
        k_msleep(SLEEP_TIME_MS);
    }
    return 0;
}

試しに次のような変更点を加えます。Lチカ周期に使用している整数リテラル浮動小数点にしてみました。ちゃんと指摘できるでしょうか?やってみます。

diff --git a/main.c b/main.c
index 4cab496..f4c5e4b 100644
--- a/main.c
+++ b/main.c
@@ -9,7 +9,7 @@
 #include <zephyr/drivers/gpio.h>
 
 /* 1000 msec = 1 sec */
-#define SLEEP_TIME_MS   1000
+#define SLEEP_TIME_MS   1000.0f
 
 /* The devicetree node identifier for the "led0" alias. */
 #define LED0_NODE DT_ALIAS(led0)

結果

実際にやってみると何度かtoolの使用確認が出てきて・・・

最終的にこのような結果が得られました。

はい、承知しました。以下の手順で進めていきます:

1. まずADRの内容を確認します
2. 次にgit diffで差分を確認します
3. 最後に差分がADRに違反していないかチェックします

まず、ADRの内容を確認しましょう:

それぞれのADRの内容を確認していきます:

次に、git diffで差分を確認します:

ADRの内容と差分を照らし合わせて、違反がないかチェックします。

この変更内容を確認したところ、以下のADRに対する明確な違反が見つかりました:

1. ADR-01「浮動小数点の使用禁止」への違反
   - 変更点で `SLEEP_TIME_MS` の値を `1000.0f` と定義していますが、これは明確に浮動小数点数(float型)の使用であり、ADR-01で禁止されている float 型の使用に該当します。
   - ADR-01では、float, double, long double の使用が明確に禁止されています。
   - 代わりに整数値の `1000` を使用するべきです。

つまり、この変更は ADR-01 に違反しており、以下のように修正する必要があります:

    ```c
    #define SLEEP_TIME_MS   1000    // 整数値を使用
    ```

その他のADRについては、この変更による違反は見られません。

ちゃんと指摘できていますね!

GitLab対応MCP Serverを使ったレビューの自動化

上記のClaudeを使ったレビュー結果を手動でGitLabにコピペするのも一つの手ですが、せっかくならレビューコメントの投稿まで自動化したいところです。

GitLabのサーバー自体はMCP純正で提供されていますが、実際のレビューに必要なToolがありませんでした。そこで、GitLab APIを利用したMCP Serverを自作することにしました。

必要なAPIは以下の3種類です: - マージリクエストの詳細取得 - マージリクエストの最新バージョン取得 - ディスカッション(レビューコメント)の投稿

実装したサーバーのコードは以下のリポジトリで公開しています:

github.com

プロンプト例

GitLabプロジェクト **project_id** の マージリクエスト **merge_request_iid** 番のコードレビューをしてもらいます。 
まず **project_dir**/adr ディレクトリに複数のADR (Architecture Decision Record) があります。それぞれのADRについて、内容を確認してください。
マージリクエストの内容を取得してください
マージリクエストのプロジェクトのgitディレクトリは **project_dir** と同期しています。一旦git fetchしてください
マージリクエストの情報から git_diff を使って targetは origin/target_branch..origin/source_branch で差分を取得してください。
この変更点に対して ADR に違反していることがないかチェックしてください。明確にADRに記載されている禁止事項があったときだけ指摘してください。指摘にあたる処理が見当たらなければここで終了です
指摘ごとに以下の手順を繰り返してください
gitの差分から指摘の行番号(複数ある場合は最後の行)を割り出してください。
そして、gitlab discussion を作成してください。
  - base_sha/head_sha/start_sha は get_merge_request_latest_version より取得できます
  - new_path, old_path は指摘したいファイルの名称です
  - new_lineは先ほど割り出した行番号です
  - old_lineは常にnullでお願いします
ADRに違反する項目の指摘事項をまとめbodyに入れてください。

結果

承知しました。順を追って作業を進めていきます。

1. まずADRの内容を確認します:

各ADRの内容を確認します:

2. マージリクエストの内容を取得します:

3. gitのfetchを実行します:

4. 差分を確認します:

5. バージョン情報を取得します:

ADRの内容とマージリクエストの差分を確認したところ、以下のADR違反が見つかりました:

1. ADR-01「浮動小数点の使用禁止」に違反しています。

コードでは `1000.0f` という浮動小数点数を使用していますが、ADR-01では明確に float の使用を禁止しています。

この違反について、GitLab discussionを作成します:

これにて、すべてのADR違反の確認と指摘が完了しました。

ちゃんと投稿できました。

完全自動レビュー対応

ここからは少し遊び心のある実装をご紹介します。実用的かどうかは読者の皆さんの判断にお任せしますが...。

現状のMCPを使ったレビューでは、以下のような手間が残っています:

  • プロジェクトIDとマージリクエストIDの指定
  • 連動するGitリポジトリディレクトリパスの指定
  • 新規チャット時の "Allow for This Chat" ボタンの連打

これらを自動化できないか考えました。そこで、MacAppleScriptを活用して完全自動化に挑戦してみました:

  1. GitLab CIのRunner環境としてMacを設定
  2. AppleScriptで:
    • Claudeの起動
    • プロンプトの入力自動化
    • "Allow for This Chat" ボタンの自動クリック

GitLab CIの設定例です。

workflow:
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' # MRイベントの場合のみ実行

auto_review:
  stage: build
  script:
    - osascript ./script/auto-reviewer.scpt ${CI_PROJECT_ID} ${CI_MERGE_REQUEST_IID} ${CI_PROJECT_DIR} & PID=$!;
    - osascript ./script/monitor-process-and-directory.scpt $PID ${CI_PROJECT_DIR}
  tags:
    - advent-calendar
  timeout: 300s

さらに、無機質になりがちなレビューコメントに遊び心を加えてみました。プロンプトに含む構文ファイルさえ用意すれば色々な構文でレビューしてくれます。

実装上の注意点

実装したスクリプトを使用する際には、以下の点に注意が必要です:

  1. Electronアプリへのアクセシビリティのため:

  2. 初期設定時の注意:

  3. パフォーマンスの課題:

    • プロンプト実行完了時にUI要素の監視が重くなる現象が発生
    • 対策として別スクリプトを並列実行
    • プロンプト完了は .done ファイルの出力で検知

実装したAppleScriptは以下のリポジトリで公開しています:

github.com

これによって誰かがMRを作成すると私のmacの画面がぴょこぴょこ動いてしまい全く実用的ではありませんが余っているMacがあれば、個性的なAIレビュワーとして活躍させてみてはいかがでしょうか?

記事の終わりに変更差分とAIレビュアーが行った指摘を並べて終わりとさせていただきます。

0除算

差分

ユーザー入力の数値で割り算を実行する

diff --git a/main.c b/main.c
index 4cab496..f92c2a2 100644
--- a/main.c
+++ b/main.c
@@ -34,6 +34,12 @@ int main(void)
                return 0;
        }
 
+       int32_t denominator;
+       printf("Enter the interval denominator: ");
+       scanf("%d", &denominator);
+
+       interval = SLEEP_TIME_MS / denominator;
+
        while (1) {
                ret = gpio_pin_toggle_dt(&led);
                if (ret < 0) {
@@ -42,7 +48,7 @@ int main(void)
 
                led_state = !led_state;
                printf("LED state: %s\n", led_state ? "ON" : "OFF");
-               k_msleep(SLEEP_TIME_MS);
+               k_msleep(interval);
        }
        return 0;
 }

指摘

ちいかわ構文

割り込みコンテキスト

差分

タイマーによるLチカをしちゃった例

diff --git a/main.c b/main.c
index 4cab496..db7cd67 100644
--- a/main.c
+++ b/main.c
@@ -19,11 +19,31 @@
  * See the sample documentation for information on how to fix this.
  */
 static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);
+struct k_timer my_timer;
+
+void timer_expiry_function(struct k_timer *dummy)
+{
+       static bool *led_state = NULL;
+
+       if (led_state == NULL) {
+               led_state = (bool *)malloc(sizeof(bool));
+               *led_state = false;
+       }
+
+       int ret = gpio_pin_toggle_dt(&led);
+       if (ret < 0) {
+               return;
+       }
+
+       led_state = !led_state;
+       printf("LED state: %s\n", led_state ? "ON" : "OFF");
+}
+
+K_TIMER_DEFINE(my_timer, timer_expiry_function, NULL);
 
 int main(void)
 {
        int ret;
-       bool led_state = true;
 
        if (!gpio_is_ready_dt(&led)) {
                return 0;
@@ -34,15 +54,10 @@ int main(void)
                return 0;
        }
 
+       k_timer_start(&my_timer, K_MSEC(SLEEP_TIME_MS), K_MSEC(SLEEP_TIME_MS));
+
        while (1) {
-               ret = gpio_pin_toggle_dt(&led);
-               if (ret < 0) {
-                       return 0;
-               }
-
-               led_state = !led_state;
-               printf("LED state: %s\n", led_state ? "ON" : "OFF");
-               k_msleep(SLEEP_TIME_MS);
+               k_sleep(K_FOREVER);
        }
        return 0;
 }

指摘

printfも使っているので複数の指摘

ちいかわ構文

mallocの指摘

おじさん構文

再帰呼び出し

差分

Lチカを再帰呼び出しで実現した意欲作

index 4cab496..46e1637 100644
--- a/main.c
+++ b/main.c
@@ -20,10 +20,26 @@
  */
 static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);
 
+void on_led(void);
+void off_led(void);
+
+void on_led(void){
+               gpio_pin_set_dt(&led, 1);
+               printf("LED state: ON");
+               k_msleep(SLEEP_TIME_MS);
+               off_led();
+}
+
+void off_led(void){
+               gpio_pin_set_dt(&led, 0);
+               printf("LED state: OFF");
+               k_msleep(SLEEP_TIME_MS);
+               on_led();
+}
+
 int main(void)
 {
        int ret;
-       bool led_state = true;
 
        if (!gpio_is_ready_dt(&led)) {
                return 0;
@@ -34,15 +50,8 @@ int main(void)
                return 0;
        }
 
-       while (1) {
-               ret = gpio_pin_toggle_dt(&led);
-               if (ret < 0) {
-                       return 0;
-               }
 
-               led_state = !led_state;
-               printf("LED state: %s\n", led_state ? "ON" : "OFF");
-               k_msleep(SLEEP_TIME_MS);
-       }
+       on_led();
+
        return 0;
 }

指摘

ツンデレ構文


株式会社フォトシンスでは、一緒にプロダクトを成長させる様々なレイヤのエンジニアを募集しています。 photosynth.co.jp Akerunにご興味のある方はこちらから akerun.com

MCP のトランスポート層に認証機能を付ける

この記事は Akerun - Qiita Advent Calendar 2024 - Qiita の 15 日目の記事です。

どうも daikw - Qiita です。

先週読んだ MCP の仕様で気になった部分があったので、実装してみました。

背景

MCP の仕様では、トランスポート層に認証機能を付けることを推奨しています。

https://modelcontextprotocol.io/docs/concepts/transports#authentication-and-authorization

Authentication and Authorization - Implement proper authentication mechanisms - Validate client credentials - Use secure token handling - Implement authorization checks

ただ、 modelcontextprotocol/python-sdk を読んでいると、まだ実装はされていないようです。

どうやらカスタムトランスポートの実装を期待しているように見えたので、その方向で実装してみます。

調査

現在までのほとんどの MCP サーバを実装した記事は、いずれも stdio Transport を使用していて、 SSE Transport のサンプルがほとんどありませんでした。

さらに言えば、現状 Claude Desktop 自体も stdio Transport しかサポートしていません。

https://github.com/orgs/modelcontextprotocol/discussions/16#discussion-7565289

modelcontextprotocol/python-sdkmcp という名前の Python パッケージとして公開されており、いくつかのサンプル実装が提供されています。

  1. uvx create-mcp-server で生成されるもの
  2. python-sdk/examples at main · modelcontextprotocol/python-sdk のサンプル

1 は stdio Transport の実装を提供します。 2 には SSE Transport の実装サンプルを提供していますが、これはそのままでは動きませんでした。

https://github.com/modelcontextprotocol/python-sdk/tree/aaf32b530738ff79ba607c2884374243350f521c/examples/servers/simple-tool

┬─[daiki@mac42:~/g/g/m/p/e/s/simple-tool][20:16:08][G:main=]
╰─>$ uv run mcp-simple-tool --transport sse --port 8000
INFO:     Started server process [67841]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
INFO:     127.0.0.1:57607 - "GET /sse HTTP/1.1" 200 OK
INFO:     127.0.0.1:57608 - "POST /messages?session_id=4ec879685e0e482e84f008362094dded HTTP/1.1" 202 Accepted
ERROR:    Exception in ASGI application
Traceback (most recent call last):
  File "/Users/daikiwatanabe/ghq/github.com/modelcontextprotocol/python-sdk/.venv/lib/python3.10/site-packages/uvicorn/protocols/http/h11_impl.py", line 406, in run_asgi
    result = await app(  # type: ignore[func-returns-value]
  File "/Users/daikiwatanabe/ghq/github.com/modelcontextprotocol/python-sdk/.venv/lib/python3.10/site-packages/uvicorn/middleware/proxy_headers.py", line 60, in __call__
    return await self.app(scope, receive, send)
  File "/Users/daikiwatanabe/ghq/github.com/modelcontextprotocol/python-sdk/.venv/lib/python3.10/site-packages/starlette/applications.py", line 113, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/Users/daikiwatanabe/ghq/github.com/modelcontextprotocol/python-sdk/.venv/lib/python3.10/site-packages/starlette/middleware/errors.py", line 187, in __call__
    raise exc
  File "/Users/daikiwatanabe/ghq/github.com/modelcontextprotocol/python-sdk/.venv/lib/python3.10/site-packages/starlette/middleware/errors.py", line 165, in __call__
    await self.app(scope, receive, _send)
  File "/Users/daikiwatanabe/ghq/github.com/modelcontextprotocol/python-sdk/.venv/lib/python3.10/site-packages/starlette/middleware/exceptions.py", line 62, in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
  File "/Users/daikiwatanabe/ghq/github.com/modelcontextprotocol/python-sdk/.venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
    raise exc
  File "/Users/daikiwatanabe/ghq/github.com/modelcontextprotocol/python-sdk/.venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 42, in wrapped_app
    await app(scope, receive, sender)
  File "/Users/daikiwatanabe/ghq/github.com/modelcontextprotocol/python-sdk/.venv/lib/python3.10/site-packages/starlette/routing.py", line 715, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/Users/daikiwatanabe/ghq/github.com/modelcontextprotocol/python-sdk/.venv/lib/python3.10/site-packages/starlette/routing.py", line 735, in app
    await route.handle(scope, receive, send)
  File "/Users/daikiwatanabe/ghq/github.com/modelcontextprotocol/python-sdk/.venv/lib/python3.10/site-packages/starlette/routing.py", line 288, in handle
    await self.app(scope, receive, send)
  File "/Users/daikiwatanabe/ghq/github.com/modelcontextprotocol/python-sdk/.venv/lib/python3.10/site-packages/starlette/routing.py", line 76, in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
  File "/Users/daikiwatanabe/ghq/github.com/modelcontextprotocol/python-sdk/.venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
    raise exc
  File "/Users/daikiwatanabe/ghq/github.com/modelcontextprotocol/python-sdk/.venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 42, in wrapped_app
    await app(scope, receive, sender)
  File "/Users/daikiwatanabe/ghq/github.com/modelcontextprotocol/python-sdk/.venv/lib/python3.10/site-packages/starlette/routing.py", line 74, in app
    await response(scope, receive, send)
TypeError: 'NoneType' object is not callable
...

実装

先ほどの ASGI サーバの振る舞いを満たさない実装は、 starlette.Routestarlette.Mount に差し替えれば動作します。 https://github.com/modelcontextprotocol/python-sdk/pull/83#pullrequestreview-2486695453

       starlette_app = Starlette(
           debug=True,
           routes=[
               Route("/sse", endpoint=handle_sse),
[+]            Mount("/messages/", app=sse.handle_post_message),
[-]            Route("/messages", endpoint=handle_messages, methods=["POST"]),
           ],
       )

その上で mcp.server.sse.SseServerTransport をラップし、 Basic 認証機能をつけます。

import base64
from contextlib import asynccontextmanager

from starlette.types import Scope, Receive, Send
from starlette.exceptions import HTTPException

from mcp.server.sse import SseServerTransport

class BasicAuthTransport(SseServerTransport):
    """
    Example basic auth implementation of SSE server transport.
    """
    def __init__(self, endpoint: str, username: str, password: str):
        super().__init__(endpoint)
        self.expected_header = b"Basic " + base64.b64encode(f"{username}:{password}".encode())

    @asynccontextmanager
    async def connect_sse(self, scope: Scope, receive: Receive, send: Send):
        auth_header = dict(scope["headers"]).get(b'authorization', b'')
        if auth_header != self.expected_header:
            raise HTTPException(status_code=401, detail="Unauthorized")
        async with super().connect_sse(scope, receive, send) as streams:
            yield streams

クライアント側にも Authorization ヘッダをつけます。

import asyncio
import base64
import click
import os
from mcp.client.session import ClientSession

from mcp.client.sse import sse_client

username = os.environ.get("USERNAME")
password = os.environ.get("PASSWORD")

async def __main(endpoint: str):
    async with sse_client(
        url=endpoint,
        headers={"Authorization": "Basic " + base64.b64encode(f"{username}:{password}".encode()).decode("utf-8")},
    ) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()

            # List available tools
            tools = await session.list_tools()
            print(tools)

            # Call the execute_command tool
            result = await session.call_tool("execute_command", {"command": "id", "args": ["-un"]})
            print("-" * 100)
            for content in result.content:
                print(content.text)


@click.command()
@click.option("--endpoint", default="http://localhost:8000/sse", help="URL of the SSE endpoint")
def main(endpoint: str):
    asyncio.run(__main(endpoint))

これでようやく動きます。

今回の実装は daikw/mcp-transport-auth に公開しました。 uv で動かせます。

git clone https://github.com/daikw/mcp-transport-auth.git
cd mcp-transport-auth

# terminal 1: server
USERNAME=admin PASSWORD=pass uv run mcp-transport-auth --port 8000

# terminal 2: client
USERNAME=admin PASSWORD=pass uv run mcp-transport-auth-client

サーバ側

USERNAME=admin PASSWORD=pass uv run mcp-transport-auth --port 8000
INFO:     Started server process [70792]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
INFO:     127.0.0.1:57863 - "GET /sse HTTP/1.1" 200 OK
INFO:     127.0.0.1:57865 - "POST /messages/?session_id=f6640184a56e43fb93f6b697bfd936b7 HTTP/1.1" 202 Accepted
INFO:     127.0.0.1:57865 - "POST /messages/?session_id=f6640184a56e43fb93f6b697bfd936b7 HTTP/1.1" 202 Accepted
INFO:     127.0.0.1:57865 - "POST /messages/?session_id=f6640184a56e43fb93f6b697bfd936b7 HTTP/1.1" 202 Accepted
INFO:mcp.server:Processing request of type ListToolsRequest
INFO:     127.0.0.1:57865 - "POST /messages/?session_id=f6640184a56e43fb93f6b697bfd936b7 HTTP/1.1" 202 Accepted
INFO:mcp.server:Processing request of type CallToolRequest
INFO:mcp_transport_auth.server:Executing command: execute_command {'command': 'id', 'args': ['-un']}
INFO:mcp_transport_auth.server:Command output: daikiwatanabe

クライアント側

USERNAME=admin PASSWORD=pass uv run mcp-transport-auth-client
nextCursor=None tools=[Tool(name='execute_command', description='Executes a command and returns its output', inputSchema={'type': 'object', 'required': ['command', 'args'], 'properties': {'command': {'type': 'string', 'description': 'Command to execute'}, 'args': {'type': 'array', 'items': {'type': 'string'}, 'description': 'Arguments to pass to the command'}}})]
----------------------------------------------------------------------------------------------------
daikiwatanabe

まとめ

  • カスタムトランスポートを実装することで、 MCP に認証機能をつけられます。
  • まだ SDK は実装が追いついていないらしく、時期尚早かもしれません。 OpenAI の圧でリリースを急いだのかも?逆に言えば OSS 貢献のチャンスかも。

参考


株式会社フォトシンスでは、一緒にプロダクトを成長させる様々なレイヤのエンジニアを募集しています。 hrmos.co

Akerun Pro の購入はこちらから akerun.com

debsecan パッケージ、Amazon Inspector、ChatGPTとの対話を経て Raspberry Pi OS の脆弱性を分析する

はじめに

この記事は Akerun Advent Calendar 2024 - Qiita の13日目の記事です。

こんにちは、SW開発部の @naritaku です。普段はゲートウェイ製品の開発をしています。

新卒で入社してから4年目を迎え、ライフサイクルや運用を意識する仕事が増えてきました。

時間がいくらあっても足りないなと思う毎日ですが、そんな時こそおろそかにしてはならないものが脆弱性の早期発見とその対策です。

最新の脆弱性情報を把握し迅速に対応することが求められますが、その他にもやりたいことがたくさんある中で致命的になるものをパパッと取り除きたい!と思い、いろいろ調べていました。

便利そうなものをいくつか試し学べたので、その知識を活かすべく今回はみんな大好き Raspberry Pi OS で脆弱性を分析してみようと思います。

やりたいこと

既存のツールをうまく使いながら、最新の Raspberry Pi脆弱性を素早く手短に調べてみます。

  • 最新の Raspberry Pi脆弱性をスキャンする
  • 出てきたCVEを使って、ログを残す
  • 「特にヤバいもの」を、リストアップさせる
  • 予防策を考える

今回利用するツール

debsecan

wiki.debian.org

Debianパッケージの脆弱性を検出できるツールです。 Raspberry Pi OS もベースはdebainなので、この仕組みに則って検出ができます。

Amazon Inspector

aws.amazon.com

公式サイトより引用すると

Amazon Inspector は、Amazon EC2 インスタンス、コンテナ、Lambda 関数などのワークロードを自動的に検出し、ソフトウェアの脆弱性や意図しないネットワークの露出がないかをスキャンします。

といった内容で、AWSのサービスと組み合わせて使うとより便利なサービスですが、今回はこのInspectorの一機能であるAmazon Inspector vulnerability databaseを参照し、脆弱性情報のデータベースとしてだけ使います。

https://nvd.nist.gov/developers/vulnerabilities などでも似たような脆弱性情報は取得できますが複数のデータベースから整ったフォーマットで情報を返してくれる点、レート制限等が他に比べてゆるい点などから採用します。

ChatGPT

GPTs の機能を使います。

openai.com

普段のChatGPTとインターフェースはほとんど変わりませんが、特定の用途に特化させたり、カスタムアクションとして外部サービスやAPIと連携できます。

今回は用途に合わせた設定を簡単に共有できる部分にメリットを感じたため採用します。

やってみる

Raspberry Pi 5 と最新の Raspberry Pi OS を使って検知をしてみます。

Raspberry Pi Imagerを使って、OSをインストールします。2024-11-19リリースのrecommendされているバージョンを対象に検査してみます。

osの選択

オプションでwifisshの設定をしておくと便利です。インストールが終わったら、sdカードを差して起動します。

まずはラズパイにdebsecanをインストールします。 いくつか依存する追加のパッケージがインストールされますがこのまま続けてしまいます。

sudo apt install debsecan

該当するCVEの数を調べてみます。

debsecan --suite bookworm --format simple

実行結果 (クリックで展開できます)

pi@rasoberrypi:~ $ debsecan --suite bookworm --format simple
CVE-2023-38469 avahi-daemon
CVE-2023-38470 avahi-daemon
CVE-2023-38471 avahi-daemon
CVE-2023-38472 avahi-daemon
CVE-2023-38473 avahi-daemon
CVE-2024-52615 avahi-daemon
CVE-2024-52616 avahi-daemon
CVE-2023-38469 avahi-utils
CVE-2023-38470 avahi-utils
CVE-2023-38471 avahi-utils
CVE-2023-38472 avahi-utils
CVE-2023-38473 avahi-utils
CVE-2024-52615 avahi-utils
CVE-2024-52616 avahi-utils
CVE-2023-27349 bluez
CVE-2023-44431 bluez
CVE-2023-45866 bluez
CVE-2023-50229 bluez
CVE-2023-50230 bluez
CVE-2023-51580 bluez
CVE-2023-51589 bluez
CVE-2023-51592 bluez
CVE-2023-51594 bluez
CVE-2023-51596 bluez
CVE-2022-48174 busybox
CVE-2023-39810 busybox
CVE-2023-42363 busybox
CVE-2023-42364 busybox
CVE-2023-42365 busybox
CVE-2023-42366 busybox
CVE-2020-15999 chromium
CVE-2020-16044 chromium
CVE-2020-15999 chromium-common
CVE-2020-16044 chromium-common
CVE-2020-15999 chromium-sandbox
CVE-2020-16044 chromium-sandbox
CVE-2016-2781 coreutils
CVE-2024-47177 cups-browsed
CVE-2024-47850 cups-browsed
CVE-2024-47177 cups-filters
CVE-2024-47850 cups-filters
CVE-2024-47177 cups-filters-core-drivers
CVE-2024-47850 cups-filters-core-drivers
CVE-2024-9681 curl
CVE-2023-28450 dnsmasq-base
CVE-2023-50387 dnsmasq-base
CVE-2023-50868 dnsmasq-base
CVE-2024-11692 firefox
CVE-2024-11694 firefox
CVE-2024-11695 firefox
CVE-2024-11696 firefox
CVE-2024-11697 firefox
CVE-2024-11699 firefox
CVE-2024-11700 firefox
CVE-2024-11701 firefox
CVE-2024-11702 firefox
CVE-2024-11703 firefox
CVE-2024-11704 firefox
CVE-2024-11705 firefox
CVE-2024-11706 firefox
CVE-2024-11708 firefox
CVE-2023-25193 gir1.2-harfbuzz-0.0
CVE-2016-2568 gir1.2-polkit-1.0
CVE-2023-37328 gstreamer1.0-alsa
CVE-2024-4453 gstreamer1.0-alsa
CVE-2023-37328 gstreamer1.0-plugins-base
CVE-2024-4453 gstreamer1.0-plugins-base
CVE-2023-37327 gstreamer1.0-plugins-good
CVE-2023-37328 gstreamer1.0-x
CVE-2024-4453 gstreamer1.0-x
CVE-2024-6655 gtk2-engines-pixbuf
CVE-2023-6879 libaom3
CVE-2023-38469 libavahi-client3
CVE-2023-38470 libavahi-client3
CVE-2023-38471 libavahi-client3
CVE-2023-38472 libavahi-client3
CVE-2023-38473 libavahi-client3
CVE-2024-52615 libavahi-client3
CVE-2024-52616 libavahi-client3
CVE-2023-38469 libavahi-common-data
CVE-2023-38470 libavahi-common-data
CVE-2023-38471 libavahi-common-data
CVE-2023-38472 libavahi-common-data
CVE-2023-38473 libavahi-common-data
CVE-2024-52615 libavahi-common-data
CVE-2024-52616 libavahi-common-data
CVE-2023-38469 libavahi-common3
CVE-2023-38470 libavahi-common3
CVE-2023-38471 libavahi-common3
CVE-2023-38472 libavahi-common3
CVE-2023-38473 libavahi-common3
CVE-2024-52615 libavahi-common3
CVE-2024-52616 libavahi-common3
CVE-2023-38469 libavahi-core7
CVE-2023-38470 libavahi-core7
CVE-2023-38471 libavahi-core7
CVE-2023-38472 libavahi-core7
CVE-2023-38473 libavahi-core7
CVE-2024-52615 libavahi-core7
CVE-2024-52616 libavahi-core7
CVE-2023-38469 libavahi-glib1
CVE-2023-38470 libavahi-glib1
CVE-2023-38471 libavahi-glib1
CVE-2023-38472 libavahi-glib1
CVE-2023-38473 libavahi-glib1
CVE-2024-52615 libavahi-glib1
CVE-2024-52616 libavahi-glib1
CVE-2020-24027 libbasicusageenvironment1
CVE-2021-28899 libbasicusageenvironment1
CVE-2021-38380 libbasicusageenvironment1
CVE-2021-38381 libbasicusageenvironment1
CVE-2021-38382 libbasicusageenvironment1
CVE-2021-39282 libbasicusageenvironment1
CVE-2021-39283 libbasicusageenvironment1
CVE-2021-41396 libbasicusageenvironment1
CVE-2023-37117 libbasicusageenvironment1
CVE-2023-27349 libbluetooth3
CVE-2023-44431 libbluetooth3
CVE-2023-45866 libbluetooth3
CVE-2023-50229 libbluetooth3
CVE-2023-50230 libbluetooth3
CVE-2023-51580 libbluetooth3
CVE-2023-51589 libbluetooth3
CVE-2023-51592 libbluetooth3
CVE-2023-51594 libbluetooth3
CVE-2023-51596 libbluetooth3
CVE-2024-7883 libc++1-16
CVE-2024-7883 libc++abi1-16
CVE-2023-4806 libc-bin
CVE-2024-2961 libc-bin
CVE-2024-33599 libc-bin
CVE-2024-33600 libc-bin
CVE-2024-33601 libc-bin
CVE-2024-33602 libc-bin
CVE-2023-4806 libc-dev-bin
CVE-2024-2961 libc-dev-bin
CVE-2024-33599 libc-dev-bin
CVE-2024-33600 libc-dev-bin
CVE-2024-33601 libc-dev-bin
CVE-2024-33602 libc-dev-bin
CVE-2023-4806 libc-devtools
CVE-2024-2961 libc-devtools
CVE-2024-33599 libc-devtools
CVE-2024-33600 libc-devtools
CVE-2024-33601 libc-devtools
CVE-2024-33602 libc-devtools
CVE-2023-4806 libc-l10n
CVE-2024-2961 libc-l10n
CVE-2024-33599 libc-l10n
CVE-2024-33600 libc-l10n
CVE-2024-33601 libc-l10n
CVE-2024-33602 libc-l10n
CVE-2023-4806 libc6
CVE-2024-2961 libc6
CVE-2024-33599 libc6
CVE-2024-33600 libc6
CVE-2024-33601 libc6
CVE-2024-33602 libc6
CVE-2023-4806 libc6-dbg
CVE-2024-2961 libc6-dbg
CVE-2024-33599 libc6-dbg
CVE-2024-33600 libc6-dbg
CVE-2024-33601 libc6-dbg
CVE-2024-33602 libc6-dbg
CVE-2023-4806 libc6-dev
CVE-2024-2961 libc6-dev
CVE-2024-33599 libc6-dev
CVE-2024-33600 libc6-dev
CVE-2024-33601 libc6-dev
CVE-2024-33602 libc6-dev
CVE-2017-7475 libcairo-gobject2
CVE-2018-18064 libcairo-gobject2
CVE-2019-6461 libcairo-gobject2
CVE-2019-6462 libcairo-gobject2
CVE-2017-7475 libcairo2
CVE-2018-18064 libcairo2
CVE-2019-6461 libcairo2
CVE-2019-6462 libcairo2
CVE-2024-47177 libcupsfilters1
CVE-2024-47850 libcupsfilters1
CVE-2024-9681 libcurl3-gnutls
CVE-2024-9681 libcurl4
CVE-2023-32570 libdav1d6
CVE-2023-51792 libde265-0
CVE-2024-38949 libde265-0
CVE-2024-38950 libde265-0
CVE-2021-46310 libdjvulibre-text
CVE-2021-46312 libdjvulibre-text
CVE-2021-46310 libdjvulibre21
CVE-2021-46312 libdjvulibre21
CVE-2023-52339 libebml5
CVE-2023-52425 libexpat1
CVE-2024-50602 libexpat1
CVE-2023-52425 libexpat1-dev
CVE-2024-50602 libexpat1-dev
CVE-2023-38857 libfaad2
CVE-2023-38858 libfaad2
CVE-2024-47177 libfontembed1
CVE-2024-47850 libfontembed1
CVE-2024-2236 libgcrypt20
CVE-2022-48340 libgfapi0
CVE-2022-48340 libgfrpc0
CVE-2022-48340 libgfxdr0
CVE-2024-52533 libglib2.0-0
CVE-2024-52533 libglib2.0-bin
CVE-2024-52533 libglib2.0-data
CVE-2022-48340 libglusterfs0
CVE-2020-24027 libgroupsock8
CVE-2021-28899 libgroupsock8
CVE-2021-38380 libgroupsock8
CVE-2021-38381 libgroupsock8
CVE-2021-38382 libgroupsock8
CVE-2021-39282 libgroupsock8
CVE-2021-39283 libgroupsock8
CVE-2021-41396 libgroupsock8
CVE-2023-37117 libgroupsock8
CVE-2023-37328 libgstreamer-gl1.0-0
CVE-2024-4453 libgstreamer-gl1.0-0
CVE-2023-37328 libgstreamer-plugins-base1.0-0
CVE-2024-4453 libgstreamer-plugins-base1.0-0
CVE-2024-6655 libgtk2.0-0
CVE-2024-6655 libgtk2.0-bin
CVE-2024-6655 libgtk2.0-common
CVE-2023-25193 libharfbuzz-icu0
CVE-2023-25193 libharfbuzz-subset0
CVE-2023-25193 libharfbuzz0b
CVE-2022-47022 libhwloc-plugins
CVE-2022-47022 libhwloc15
CVE-2020-12695 libixml10
CVE-2020-13848 libixml10
CVE-2021-28302 libixml10
CVE-2021-29462 libixml10
CVE-2023-0645 libjxl0.7
CVE-2023-35790 libjxl0.7
CVE-2024-11403 libjxl0.7
CVE-2024-11498 libjxl0.7
CVE-2024-25262 libkpathsea6
CVE-2023-2953 libldap-2.5-0
CVE-2023-2953 libldap-common
CVE-2018-14628 libldb2
CVE-2020-24027 liblivemedia77
CVE-2021-28899 liblivemedia77
CVE-2021-38380 liblivemedia77
CVE-2021-38381 liblivemedia77
CVE-2021-38382 liblivemedia77
CVE-2021-39282 liblivemedia77
CVE-2021-39283 liblivemedia77
CVE-2021-41396 liblivemedia77
CVE-2023-37117 liblivemedia77
CVE-2024-7883 libllvm15
CVE-2024-23170 libmbedcrypto7
CVE-2024-23775 libmbedcrypto7
CVE-2024-28755 libmbedcrypto7
CVE-2024-28960 libmbedcrypto7
CVE-2023-5215 libnbd0
CVE-2024-7383 libnbd0
CVE-2023-50495 libncurses6
CVE-2023-50495 libncursesw6
CVE-2024-6501 libnm0
CVE-2023-5388 libnss3
CVE-2023-6135 libnss3
CVE-2024-31047 libopenexr-3-1-30
CVE-2019-6988 libopenjp2-7
CVE-2021-3575 libopenjp2-7
CVE-2023-39327 libopenjp2-7
CVE-2023-39328 libopenjp2-7
CVE-2023-39329 libopenjp2-7
CVE-2024-40897 liborc-0.4-0
CVE-2024-10041 libpam-chksshpwd
CVE-2024-10963 libpam-chksshpwd
CVE-2024-22365 libpam-chksshpwd
CVE-2024-10041 libpam-modules
CVE-2024-10963 libpam-modules
CVE-2024-22365 libpam-modules
CVE-2024-10041 libpam-modules-bin
CVE-2024-10963 libpam-modules-bin
CVE-2024-22365 libpam-modules-bin
CVE-2024-10041 libpam-runtime
CVE-2024-10963 libpam-runtime
CVE-2024-22365 libpam-runtime
CVE-2024-10041 libpam0g
CVE-2024-10963 libpam0g
CVE-2024-22365 libpam0g
CVE-2023-31484 libperl5.36
CVE-2016-2568 libpolkit-agent-1-0
CVE-2016-2568 libpolkit-gobject-1-0
CVE-2019-9543 libpoppler-cpp0v5
CVE-2019-9545 libpoppler-cpp0v5
CVE-2019-9543 libpoppler-glib8
CVE-2019-9545 libpoppler-glib8
CVE-2019-9543 libpoppler126
CVE-2019-9545 libpoppler126
CVE-2023-4016 libproc2-0
CVE-2024-7254 libprotobuf-lite32
CVE-2024-7254 libprotobuf32
CVE-2023-27043 libpython3.11
CVE-2024-11168 libpython3.11
CVE-2024-6923 libpython3.11
CVE-2024-7592 libpython3.11
CVE-2024-9287 libpython3.11
CVE-2023-27043 libpython3.11-dev
CVE-2024-11168 libpython3.11-dev
CVE-2024-6923 libpython3.11-dev
CVE-2024-7592 libpython3.11-dev
CVE-2024-9287 libpython3.11-dev
CVE-2023-27043 libpython3.11-minimal
CVE-2024-11168 libpython3.11-minimal
CVE-2024-6923 libpython3.11-minimal
CVE-2024-7592 libpython3.11-minimal
CVE-2024-9287 libpython3.11-minimal
CVE-2023-27043 libpython3.11-stdlib
CVE-2024-11168 libpython3.11-stdlib
CVE-2024-6923 libpython3.11-stdlib
CVE-2024-7592 libpython3.11-stdlib
CVE-2024-9287 libpython3.11-stdlib
CVE-2024-39936 libqt5core5a
CVE-2024-39936 libqt5dbus5
CVE-2024-39936 libqt5gui5
CVE-2024-39936 libqt5network5
CVE-2024-39936 libqt5printsupport5
CVE-2024-39936 libqt5sql5
CVE-2024-39936 libqt5sql5-sqlite
CVE-2024-39936 libqt5test5
CVE-2024-39936 libqt5widgets5
CVE-2024-39936 libqt5xml5
CVE-2023-35789 librabbitmq4
CVE-2023-43040 librados2
CVE-2023-43040 librbd1
CVE-2018-14628 libsmbclient
CVE-2022-33064 libsndfile1
CVE-2022-33065 libsndfile1
CVE-2024-50612 libsndfile1
CVE-2024-50613 libsndfile1
CVE-2024-52530 libsoup-3.0-0
CVE-2024-52531 libsoup-3.0-0
CVE-2024-52532 libsoup-3.0-0
CVE-2024-52530 libsoup-3.0-common
CVE-2024-52531 libsoup-3.0-common
CVE-2024-52532 libsoup-3.0-common
CVE-2024-52530 libsoup2.4-1
CVE-2024-52531 libsoup2.4-1
CVE-2024-52532 libsoup2.4-1
CVE-2024-52530 libsoup2.4-common
CVE-2024-52531 libsoup2.4-common
CVE-2024-52532 libsoup2.4-common
CVE-2024-0727 libssl3
CVE-2024-2511 libssl3
CVE-2024-4741 libssl3
CVE-2024-5535 libssl3
CVE-2024-9143 libssl3
CVE-2024-25262 libsynctex2
CVE-2023-25433 libtiff6
CVE-2023-26965 libtiff6
CVE-2023-26966 libtiff6
CVE-2023-2908 libtiff6
CVE-2023-3618 libtiff6
CVE-2023-52355 libtiff6
CVE-2023-52356 libtiff6
CVE-2023-6277 libtiff6
CVE-2024-7006 libtiff6
CVE-2023-50495 libtinfo6
CVE-2024-33655 libunbound8
CVE-2024-8508 libunbound8
CVE-2024-7883 libunwind-16
CVE-2020-12695 libupnp13
CVE-2020-13848 libupnp13
CVE-2021-28302 libupnp13
CVE-2021-29462 libupnp13
CVE-2020-24027 libusageenvironment3
CVE-2021-28899 libusageenvironment3
CVE-2021-38380 libusageenvironment3
CVE-2021-38381 libusageenvironment3
CVE-2021-38382 libusageenvironment3
CVE-2021-39282 libusageenvironment3
CVE-2021-39283 libusageenvironment3
CVE-2021-41396 libusageenvironment3
CVE-2023-37117 libusageenvironment3
CVE-2024-37535 libvte-2.91-0
CVE-2024-37535 libvte-2.91-common
CVE-2018-14628 libwbclient0
CVE-2023-39615 libxml2
CVE-2023-45322 libxml2
CVE-2024-25062 libxml2
CVE-2013-7445 linux-headers-6.6.51+rpt-common-rpi
CVE-2018-12928 linux-headers-6.6.51+rpt-common-rpi
CVE-2019-19449 linux-headers-6.6.51+rpt-common-rpi
CVE-2019-19814 linux-headers-6.6.51+rpt-common-rpi
CVE-2019-20794 linux-headers-6.6.51+rpt-common-rpi
CVE-2020-14304 linux-headers-6.6.51+rpt-common-rpi
CVE-2020-36694 linux-headers-6.6.51+rpt-common-rpi
CVE-2021-3847 linux-headers-6.6.51+rpt-common-rpi
CVE-2021-3864 linux-headers-6.6.51+rpt-common-rpi
CVE-2022-4543 linux-headers-6.6.51+rpt-common-rpi
CVE-2023-0160 linux-headers-6.6.51+rpt-common-rpi
CVE-2023-31082 linux-headers-6.6.51+rpt-common-rpi
CVE-2023-3397 linux-headers-6.6.51+rpt-common-rpi
CVE-2023-37454 linux-headers-6.6.51+rpt-common-rpi
CVE-2023-4010 linux-headers-6.6.51+rpt-common-rpi
CVE-2023-6240 linux-headers-6.6.51+rpt-common-rpi
CVE-2024-0564 linux-headers-6.6.51+rpt-common-rpi
CVE-2024-21803 linux-headers-6.6.51+rpt-common-rpi
CVE-2024-2193 linux-headers-6.6.51+rpt-common-rpi
CVE-2024-22386 linux-headers-6.6.51+rpt-common-rpi
CVE-2024-24864 linux-headers-6.6.51+rpt-common-rpi
CVE-2024-25740 linux-headers-6.6.51+rpt-common-rpi
CVE-2013-7445 linux-headers-6.6.51+rpt-rpi-2712
CVE-2018-12928 linux-headers-6.6.51+rpt-rpi-2712
CVE-2019-19449 linux-headers-6.6.51+rpt-rpi-2712
CVE-2019-19814 linux-headers-6.6.51+rpt-rpi-2712
CVE-2019-20794 linux-headers-6.6.51+rpt-rpi-2712
CVE-2020-14304 linux-headers-6.6.51+rpt-rpi-2712
CVE-2020-36694 linux-headers-6.6.51+rpt-rpi-2712
CVE-2021-3847 linux-headers-6.6.51+rpt-rpi-2712
CVE-2021-3864 linux-headers-6.6.51+rpt-rpi-2712
CVE-2022-4543 linux-headers-6.6.51+rpt-rpi-2712
CVE-2023-0160 linux-headers-6.6.51+rpt-rpi-2712
CVE-2023-31082 linux-headers-6.6.51+rpt-rpi-2712
CVE-2023-3397 linux-headers-6.6.51+rpt-rpi-2712
CVE-2023-37454 linux-headers-6.6.51+rpt-rpi-2712
CVE-2023-4010 linux-headers-6.6.51+rpt-rpi-2712
CVE-2023-6240 linux-headers-6.6.51+rpt-rpi-2712
CVE-2024-0564 linux-headers-6.6.51+rpt-rpi-2712
CVE-2024-21803 linux-headers-6.6.51+rpt-rpi-2712
CVE-2024-2193 linux-headers-6.6.51+rpt-rpi-2712
CVE-2024-22386 linux-headers-6.6.51+rpt-rpi-2712
CVE-2024-24864 linux-headers-6.6.51+rpt-rpi-2712
CVE-2024-25740 linux-headers-6.6.51+rpt-rpi-2712
CVE-2013-7445 linux-headers-6.6.51+rpt-rpi-v8
CVE-2018-12928 linux-headers-6.6.51+rpt-rpi-v8
CVE-2019-19449 linux-headers-6.6.51+rpt-rpi-v8
CVE-2019-19814 linux-headers-6.6.51+rpt-rpi-v8
CVE-2019-20794 linux-headers-6.6.51+rpt-rpi-v8
CVE-2020-14304 linux-headers-6.6.51+rpt-rpi-v8
CVE-2020-36694 linux-headers-6.6.51+rpt-rpi-v8
CVE-2021-3847 linux-headers-6.6.51+rpt-rpi-v8
CVE-2021-3864 linux-headers-6.6.51+rpt-rpi-v8
CVE-2022-4543 linux-headers-6.6.51+rpt-rpi-v8
CVE-2023-0160 linux-headers-6.6.51+rpt-rpi-v8
CVE-2023-31082 linux-headers-6.6.51+rpt-rpi-v8
CVE-2023-3397 linux-headers-6.6.51+rpt-rpi-v8
CVE-2023-37454 linux-headers-6.6.51+rpt-rpi-v8
CVE-2023-4010 linux-headers-6.6.51+rpt-rpi-v8
CVE-2023-6240 linux-headers-6.6.51+rpt-rpi-v8
CVE-2024-0564 linux-headers-6.6.51+rpt-rpi-v8
CVE-2024-21803 linux-headers-6.6.51+rpt-rpi-v8
CVE-2024-2193 linux-headers-6.6.51+rpt-rpi-v8
CVE-2024-22386 linux-headers-6.6.51+rpt-rpi-v8
CVE-2024-24864 linux-headers-6.6.51+rpt-rpi-v8
CVE-2024-25740 linux-headers-6.6.51+rpt-rpi-v8
CVE-2013-7445 linux-headers-rpi-2712
CVE-2018-12928 linux-headers-rpi-2712
CVE-2019-19449 linux-headers-rpi-2712
CVE-2019-19814 linux-headers-rpi-2712
CVE-2019-20794 linux-headers-rpi-2712
CVE-2020-14304 linux-headers-rpi-2712
CVE-2020-36694 linux-headers-rpi-2712
CVE-2021-3847 linux-headers-rpi-2712
CVE-2021-3864 linux-headers-rpi-2712
CVE-2022-4543 linux-headers-rpi-2712
CVE-2023-0160 linux-headers-rpi-2712
CVE-2023-31082 linux-headers-rpi-2712
CVE-2023-3397 linux-headers-rpi-2712
CVE-2023-37454 linux-headers-rpi-2712
CVE-2023-4010 linux-headers-rpi-2712
CVE-2023-6240 linux-headers-rpi-2712
CVE-2024-0564 linux-headers-rpi-2712
CVE-2024-21803 linux-headers-rpi-2712
CVE-2024-2193 linux-headers-rpi-2712
CVE-2024-22386 linux-headers-rpi-2712
CVE-2024-24864 linux-headers-rpi-2712
CVE-2024-25740 linux-headers-rpi-2712
CVE-2013-7445 linux-headers-rpi-v8
CVE-2018-12928 linux-headers-rpi-v8
CVE-2019-19449 linux-headers-rpi-v8
CVE-2019-19814 linux-headers-rpi-v8
CVE-2019-20794 linux-headers-rpi-v8
CVE-2020-14304 linux-headers-rpi-v8
CVE-2020-36694 linux-headers-rpi-v8
CVE-2021-3847 linux-headers-rpi-v8
CVE-2021-3864 linux-headers-rpi-v8
CVE-2022-4543 linux-headers-rpi-v8
CVE-2023-0160 linux-headers-rpi-v8
CVE-2023-31082 linux-headers-rpi-v8
CVE-2023-3397 linux-headers-rpi-v8
CVE-2023-37454 linux-headers-rpi-v8
CVE-2023-4010 linux-headers-rpi-v8
CVE-2023-6240 linux-headers-rpi-v8
CVE-2024-0564 linux-headers-rpi-v8
CVE-2024-21803 linux-headers-rpi-v8
CVE-2024-2193 linux-headers-rpi-v8
CVE-2024-22386 linux-headers-rpi-v8
CVE-2024-24864 linux-headers-rpi-v8
CVE-2024-25740 linux-headers-rpi-v8
CVE-2013-7445 linux-image-6.6.51+rpt-rpi-2712
CVE-2018-12928 linux-image-6.6.51+rpt-rpi-2712
CVE-2019-19449 linux-image-6.6.51+rpt-rpi-2712
CVE-2019-19814 linux-image-6.6.51+rpt-rpi-2712
CVE-2019-20794 linux-image-6.6.51+rpt-rpi-2712
CVE-2020-14304 linux-image-6.6.51+rpt-rpi-2712
CVE-2020-36694 linux-image-6.6.51+rpt-rpi-2712
CVE-2021-3847 linux-image-6.6.51+rpt-rpi-2712
CVE-2021-3864 linux-image-6.6.51+rpt-rpi-2712
CVE-2022-4543 linux-image-6.6.51+rpt-rpi-2712
CVE-2023-0160 linux-image-6.6.51+rpt-rpi-2712
CVE-2023-31082 linux-image-6.6.51+rpt-rpi-2712
CVE-2023-3397 linux-image-6.6.51+rpt-rpi-2712
CVE-2023-37454 linux-image-6.6.51+rpt-rpi-2712
CVE-2023-4010 linux-image-6.6.51+rpt-rpi-2712
CVE-2023-6240 linux-image-6.6.51+rpt-rpi-2712
CVE-2024-0564 linux-image-6.6.51+rpt-rpi-2712
CVE-2024-21803 linux-image-6.6.51+rpt-rpi-2712
CVE-2024-2193 linux-image-6.6.51+rpt-rpi-2712
CVE-2024-22386 linux-image-6.6.51+rpt-rpi-2712
CVE-2024-24864 linux-image-6.6.51+rpt-rpi-2712
CVE-2024-25740 linux-image-6.6.51+rpt-rpi-2712
CVE-2013-7445 linux-image-6.6.51+rpt-rpi-v8
CVE-2018-12928 linux-image-6.6.51+rpt-rpi-v8
CVE-2019-19449 linux-image-6.6.51+rpt-rpi-v8
CVE-2019-19814 linux-image-6.6.51+rpt-rpi-v8
CVE-2019-20794 linux-image-6.6.51+rpt-rpi-v8
CVE-2020-14304 linux-image-6.6.51+rpt-rpi-v8
CVE-2020-36694 linux-image-6.6.51+rpt-rpi-v8
CVE-2021-3847 linux-image-6.6.51+rpt-rpi-v8
CVE-2021-3864 linux-image-6.6.51+rpt-rpi-v8
CVE-2022-4543 linux-image-6.6.51+rpt-rpi-v8
CVE-2023-0160 linux-image-6.6.51+rpt-rpi-v8
CVE-2023-31082 linux-image-6.6.51+rpt-rpi-v8
CVE-2023-3397 linux-image-6.6.51+rpt-rpi-v8
CVE-2023-37454 linux-image-6.6.51+rpt-rpi-v8
CVE-2023-4010 linux-image-6.6.51+rpt-rpi-v8
CVE-2023-6240 linux-image-6.6.51+rpt-rpi-v8
CVE-2024-0564 linux-image-6.6.51+rpt-rpi-v8
CVE-2024-21803 linux-image-6.6.51+rpt-rpi-v8
CVE-2024-2193 linux-image-6.6.51+rpt-rpi-v8
CVE-2024-22386 linux-image-6.6.51+rpt-rpi-v8
CVE-2024-24864 linux-image-6.6.51+rpt-rpi-v8
CVE-2024-25740 linux-image-6.6.51+rpt-rpi-v8
CVE-2013-7445 linux-image-rpi-2712
CVE-2018-12928 linux-image-rpi-2712
CVE-2019-19449 linux-image-rpi-2712
CVE-2019-19814 linux-image-rpi-2712
CVE-2019-20794 linux-image-rpi-2712
CVE-2020-14304 linux-image-rpi-2712
CVE-2020-36694 linux-image-rpi-2712
CVE-2021-3847 linux-image-rpi-2712
CVE-2021-3864 linux-image-rpi-2712
CVE-2022-4543 linux-image-rpi-2712
CVE-2023-0160 linux-image-rpi-2712
CVE-2023-31082 linux-image-rpi-2712
CVE-2023-3397 linux-image-rpi-2712
CVE-2023-37454 linux-image-rpi-2712
CVE-2023-4010 linux-image-rpi-2712
CVE-2023-6240 linux-image-rpi-2712
CVE-2024-0564 linux-image-rpi-2712
CVE-2024-21803 linux-image-rpi-2712
CVE-2024-2193 linux-image-rpi-2712
CVE-2024-22386 linux-image-rpi-2712
CVE-2024-24864 linux-image-rpi-2712
CVE-2024-25740 linux-image-rpi-2712
CVE-2013-7445 linux-image-rpi-v8
CVE-2018-12928 linux-image-rpi-v8
CVE-2019-19449 linux-image-rpi-v8
CVE-2019-19814 linux-image-rpi-v8
CVE-2019-20794 linux-image-rpi-v8
CVE-2020-14304 linux-image-rpi-v8
CVE-2020-36694 linux-image-rpi-v8
CVE-2021-3847 linux-image-rpi-v8
CVE-2021-3864 linux-image-rpi-v8
CVE-2022-4543 linux-image-rpi-v8
CVE-2023-0160 linux-image-rpi-v8
CVE-2023-31082 linux-image-rpi-v8
CVE-2023-3397 linux-image-rpi-v8
CVE-2023-37454 linux-image-rpi-v8
CVE-2023-4010 linux-image-rpi-v8
CVE-2023-6240 linux-image-rpi-v8
CVE-2024-0564 linux-image-rpi-v8
CVE-2024-21803 linux-image-rpi-v8
CVE-2024-2193 linux-image-rpi-v8
CVE-2024-22386 linux-image-rpi-v8
CVE-2024-24864 linux-image-rpi-v8
CVE-2024-25740 linux-image-rpi-v8
CVE-2013-7445 linux-kbuild-6.6.51+rpt
CVE-2018-12928 linux-kbuild-6.6.51+rpt
CVE-2019-19449 linux-kbuild-6.6.51+rpt
CVE-2019-19814 linux-kbuild-6.6.51+rpt
CVE-2019-20794 linux-kbuild-6.6.51+rpt
CVE-2020-14304 linux-kbuild-6.6.51+rpt
CVE-2020-36694 linux-kbuild-6.6.51+rpt
CVE-2021-3847 linux-kbuild-6.6.51+rpt
CVE-2021-3864 linux-kbuild-6.6.51+rpt
CVE-2022-4543 linux-kbuild-6.6.51+rpt
CVE-2023-0160 linux-kbuild-6.6.51+rpt
CVE-2023-31082 linux-kbuild-6.6.51+rpt
CVE-2023-3397 linux-kbuild-6.6.51+rpt
CVE-2023-37454 linux-kbuild-6.6.51+rpt
CVE-2023-4010 linux-kbuild-6.6.51+rpt
CVE-2023-6240 linux-kbuild-6.6.51+rpt
CVE-2024-0564 linux-kbuild-6.6.51+rpt
CVE-2024-21803 linux-kbuild-6.6.51+rpt
CVE-2024-2193 linux-kbuild-6.6.51+rpt
CVE-2024-22386 linux-kbuild-6.6.51+rpt
CVE-2024-24864 linux-kbuild-6.6.51+rpt
CVE-2024-25740 linux-kbuild-6.6.51+rpt
CVE-2013-7445 linux-libc-dev
CVE-2018-12928 linux-libc-dev
CVE-2019-19449 linux-libc-dev
CVE-2019-19814 linux-libc-dev
CVE-2019-20794 linux-libc-dev
CVE-2020-14304 linux-libc-dev
CVE-2020-36694 linux-libc-dev
CVE-2021-3847 linux-libc-dev
CVE-2021-3864 linux-libc-dev
CVE-2022-4543 linux-libc-dev
CVE-2023-0160 linux-libc-dev
CVE-2023-31082 linux-libc-dev
CVE-2023-3397 linux-libc-dev
CVE-2023-37454 linux-libc-dev
CVE-2023-4010 linux-libc-dev
CVE-2023-6240 linux-libc-dev
CVE-2024-0564 linux-libc-dev
CVE-2024-21803 linux-libc-dev
CVE-2024-2193 linux-libc-dev
CVE-2024-22386 linux-libc-dev
CVE-2024-24864 linux-libc-dev
CVE-2024-25740 linux-libc-dev
CVE-2023-4806 locales
CVE-2024-2961 locales
CVE-2024-33599 locales
CVE-2024-33600 locales
CVE-2024-33601 locales
CVE-2024-33602 locales
CVE-2023-29383 login
CVE-2023-4641 login
CVE-2023-50495 ncurses-base
CVE-2023-50495 ncurses-bin
CVE-2023-50495 ncurses-term
CVE-2024-6501 network-manager
CVE-2024-0727 openssl
CVE-2024-2511 openssl
CVE-2024-4741 openssl
CVE-2024-5535 openssl
CVE-2024-9143 openssl
CVE-2023-52168 p7zip
CVE-2024-11612 p7zip
CVE-2023-52168 p7zip-full
CVE-2024-11612 p7zip-full
CVE-2023-29383 passwd
CVE-2023-4641 passwd
CVE-2023-31484 perl
CVE-2023-31484 perl-base
CVE-2023-31484 perl-modules-5.36
CVE-2016-2568 pkexec
CVE-2016-2568 policykit-1
CVE-2016-2568 polkitd
CVE-2016-2568 polkitd-pkla
CVE-2019-9543 poppler-utils
CVE-2019-9545 poppler-utils
CVE-2023-4016 procps
CVE-2023-50782 python3-cryptography
CVE-2024-22195 python3-jinja2
CVE-2024-34064 python3-jinja2
CVE-2023-5752 python3-pip
CVE-2023-5752 python3-pip-whl
CVE-2024-6345 python3-pkg-resources
CVE-2023-52323 python3-pycryptodome
CVE-2022-40896 python3-pygments
CVE-2023-32681 python3-requests
CVE-2024-35195 python3-requests
CVE-2024-6345 python3-setuptools
CVE-2024-6345 python3-setuptools-whl
CVE-2024-34062 python3-tqdm
CVE-2023-43804 python3-urllib3
CVE-2023-45803 python3-urllib3
CVE-2024-37891 python3-urllib3
CVE-2024-34069 python3-werkzeug
CVE-2024-5569 python3-zipp
CVE-2023-27043 python3.11
CVE-2024-11168 python3.11
CVE-2024-6923 python3.11
CVE-2024-7592 python3.11
CVE-2024-9287 python3.11
CVE-2023-27043 python3.11-dev
CVE-2024-11168 python3.11-dev
CVE-2024-6923 python3.11-dev
CVE-2024-7592 python3.11-dev
CVE-2024-9287 python3.11-dev
CVE-2023-27043 python3.11-minimal
CVE-2024-11168 python3.11-minimal
CVE-2024-6923 python3.11-minimal
CVE-2024-7592 python3.11-minimal
CVE-2024-9287 python3.11-minimal
CVE-2023-27043 python3.11-venv
CVE-2024-11168 python3.11-venv
CVE-2024-6923 python3.11-venv
CVE-2024-7592 python3.11-venv
CVE-2024-9287 python3.11-venv
CVE-2024-39936 qt5-gtk-platformtheme
CVE-2018-14628 samba-libs
CVE-2023-42465 sudo
CVE-2023-2610 vim-common
CVE-2023-4738 vim-common
CVE-2023-4752 vim-common
CVE-2023-4781 vim-common
CVE-2023-5344 vim-common
CVE-2024-22667 vim-common
CVE-2024-43802 vim-common
CVE-2024-47814 vim-common
CVE-2023-2610 vim-tiny
CVE-2023-4738 vim-tiny
CVE-2023-4752 vim-tiny
CVE-2023-4781 vim-tiny
CVE-2023-5344 vim-tiny
CVE-2024-22667 vim-tiny
CVE-2024-43802 vim-tiny
CVE-2024-47814 vim-tiny
CVE-2021-31879 wget
CVE-2024-10524 wget
CVE-2024-38428 wget
CVE-2020-27748 xdg-utils
CVE-2022-4055 xdg-utils
CVE-2023-5367 xserver-common
CVE-2023-5380 xserver-common
CVE-2023-5574 xserver-common
CVE-2023-6377 xserver-common
CVE-2023-6478 xserver-common
CVE-2023-6816 xserver-common
CVE-2024-0229 xserver-common
CVE-2024-0408 xserver-common
CVE-2024-0409 xserver-common
CVE-2024-21885 xserver-common
CVE-2024-21886 xserver-common
CVE-2024-31080 xserver-common
CVE-2024-31081 xserver-common
CVE-2024-31083 xserver-common
CVE-2024-9632 xserver-common
CVE-2023-5367 xserver-xorg-core
CVE-2023-5380 xserver-xorg-core
CVE-2023-5574 xserver-xorg-core
CVE-2023-6377 xserver-xorg-core
CVE-2023-6478 xserver-xorg-core
CVE-2023-6816 xserver-xorg-core
CVE-2024-0229 xserver-xorg-core
CVE-2024-0408 xserver-xorg-core
CVE-2024-0409 xserver-xorg-core
CVE-2024-21885 xserver-xorg-core
CVE-2024-21886 xserver-xorg-core
CVE-2024-31080 xserver-xorg-core
CVE-2024-31081 xserver-xorg-core
CVE-2024-31083 xserver-xorg-core
CVE-2024-9632 xserver-xorg-core
CVE-2023-5367 xwayland
CVE-2023-6377 xwayland
CVE-2023-6478 xwayland
CVE-2023-6816 xwayland
CVE-2024-0229 xwayland
CVE-2024-0408 xwayland
CVE-2024-0409 xwayland
CVE-2024-21885 xwayland
CVE-2024-21886 xwayland
CVE-2024-31080 xwayland
CVE-2024-31081 xwayland
CVE-2024-31083 xwayland
CVE-2024-9632 xwayland
CVE-2023-45853 zlib1g
CVE-2023-45853 zlib1g-dev

ユニークなCVEの報告数は230、報告数の多いパッケージTOP15は次のようになっていました。

# ユニークなCVEの報告数
pi@rasoberrypi:~ $ debsecan --suite bookworm --format simple  | awk '{print $1}' | sort | uniq -c | sort -nr |wc -l
230

# パッケージごとの脆弱性報告数 top15
pi@rasoberrypi:~ $ debsecan --suite bookworm --format simple | awk '{print $2}' | sort | uniq -c | sort -nr | awk '{print $2 " " $1}' | head -n 15
linux-libc-dev 22
linux-kbuild-6.6.51+rpt 22
linux-image-rpi-v8 22
linux-image-rpi-2712 22
linux-image-6.6.51+rpt-rpi-v8 22
linux-image-6.6.51+rpt-rpi-2712 22
linux-headers-rpi-v8 22
linux-headers-rpi-2712 22
linux-headers-6.6.51+rpt-rpi-v8 22
linux-headers-6.6.51+rpt-rpi-2712 22
linux-headers-6.6.51+rpt-common-rpi 22
xserver-xorg-core 15
xserver-common 15
firefox 14
xwayland 13

大量に出てしまうとどれから手をつけていいのかがわからなくなってしまいます。  CVEの1つ1つを深さ優先で情報を掘るといくら時間があっても終わらなくなってしまうので、幅優先で判断する情報を足してみます。

ちょうど個人の何も運用できていなかったawsアカウントがあるのでamazon inspectorのVulnerability database search の機能を利用します。

まずはinspectorの有効化します。EC2 インスタンスなどinspectorのチェック対象がいなければ、料金がかからないです。お財布に優しいですね。

料金 - Amazon Inspector | AWS

IAMユーザーを作成し、権限としてAmazonInspector2ReadOnlyAccessを与えておきます.

iamユーザーへ権限の付与

IAMユーザーにアクセスキーを発行し、aws cli コマンドが叩ける別環境から確認します。

aws inspector2 search-vulnerabilities --filter-criteria '{"vulnerabilityIds":["CVE-2023-38469"]}'
{
    "vulnerabilities": [
        {
            "atigData": {},
            "cvss3": {
                "baseScore": 5.5,
                "scoringVector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H"
            },
            "cwes": [
                "CWE-617"
            ],
            "description": "A vulnerability was found in Avahi, where a reachable assertion exists in avahi_dns_packet_append_record.",
            "detectionPlatforms": [
                "ALPINE_LINUX_3_19",
                "ALPINE_LINUX_3_20",
                "AMAZON_LINUX_2023",
                "AMAZON_LINUX_2",
                "UBUNTU_14_04",
                "UBUNTU_23_04",
                "UBUNTU_18_04",
                "UBUNTU_20_04",
                "UBUNTU_22_04",
                "UBUNTU_16_04",
                "UBUNTU_23_10",
                "ALMALINUX_9",
                "ALMALINUX_8",
                "DEBIAN_SID",
                "OPEN_SUSE_15_6",
                "OPEN_SUSE_15_5",
                "SUSE_SERVER_12_5",
                "SUSE_SERVER_15_5",
                "SUSE_SERVER_15_6",
                "ROCKY_8",
                "WOLFI",
                "AMAZON_LINUX",
                "ORACLE_LINUX_8",
                "ORACLE_LINUX_9",
                "RHEL_8",
                "RHEL_9"
            ],
            "epss": {
                "score": 0.00043
            },
            "id": "CVE-2023-38469",
            "referenceUrls": [
                "https://access.redhat.com/security/cve/CVE-2023-38469",
                "https://bugzilla.redhat.com/show_bug.cgi?id=2191687"
            ],
            "relatedVulnerabilities": [],
            "source": "NVD",
            "sourceUrl": "https://nvd.nist.gov/vuln/detail/CVE-2023-38469",
            "vendorCreatedAt": "2023-11-03T00:15:08+09:00",
            "vendorSeverity": "MEDIUM",
            "vendorUpdatedAt": "2023-11-10T04:58:53+09:00"
        }
    ]
}

これでデータの抽出は簡単にできました。 cvss3やepssの情報を使うと脆弱性影響度や対応の優先度の指標になりますね。

少々時間がかかりますが、ユニークなCVEのリストから、順にコマンドを叩いてcsv形式で保存しておきます。

端末内部でのユニークなCVEのリスト取得

debsecan --suite bookworm --format simple  | awk '{print $1}' | sort | uniq > cve.list

解析用のshell gist.github.com

実行結果

$ ./cve2csv.sh
89 "UNKNOWN"
76 "MEDIUM"
52 "HIGH"
9 "CRITICAL"
3 "LOW"


$ head -n 3 ./cve_raspberry.csv
"CVE-2013-7445","HIGH",7.8,"AV:N/AC:L/Au:N/C:N/I:N/A:C",0.00149,"The Direct Rendering Manager (DRM) subsystem in the Linux kernel through 4.x mishandles requests for Graphics Execution Manager (GEM) objects, which allows context-dependent attackers to cause a denial of service (memory consumption) via an application that processes graphics data, as demonstrated by JavaScript code that creates many CANVAS elements for rendering by Chrome or Firefox.","https://nvd.nist.gov/vuln/detail/CVE-2013-7445","https://bugzilla.kernel.org/show_bug.cgi?id=60533"
"CVE-2016-2568","HIGH",7.8,"CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:H",0.00042,"pkexec, when used with --user nonpriv, allows local users to escape to the parent session via a crafted TIOCSTI ioctl call, which pushes characters to the terminal's input buffer.","https://nvd.nist.gov/vuln/detail/CVE-2016-2568","https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=816062,https://bugzilla.redhat.com/show_bug.cgi?id=1300746,https://access.redhat.com/security/cve/cve-2016-2568,https://ubuntu.com/security/CVE-2016-2568"
"CVE-2016-2781","MEDIUM",6.5,"CVSS:3.0/AV:L/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:N",0.00042,"chroot in GNU coreutils, when used with --userspec, allows local users to escape to the parent session via a crafted TIOCSTI ioctl call, which pushes characters to the terminal's input buffer.","https://nvd.nist.gov/vuln/detail/CVE-2016-2781","https://lists.apache.org/thread.html/rf9fa47ab66495c78bb4120b0754dd9531ca2ff0430f6685ac9b07772@<dev.mina.apache.org>"

CVEによっては、まだ脆弱性が見つかったばかりで詳細なデータが足りていないものもありますが、取得できたデータを使って分析してみます。

ChatGPTと戯れながら、分析する

GPTsを使ってみます

https://chatgpt.com/gptscveと検索すると似たようなGPTsはすでにいくつかありましたが、勉強のために自分でも作ってみました。

chatgpt.com

先ほど作ったcsvを読み込ませて幾つか質問していきます。

脆弱性の優先順位をGPTsに考えてもらう

ChatGPT - CVE優先順位決定

1人でない安心感と、1つずつ解決できそうな自信が出てきました。

対策してみる

脆弱性の数の多さで、気持ち的にやられてしまいそうになりましたが、GPTsがいればもう少しだけ対応を考えられそうです。 とはいえ、脆弱性の数を一気に減らせそうな取り組みを考えてみます。

不要なパッケージのアンインストール

debsecanで見つけらる脆弱性Debianパッケージのバージョン情報等に基づいています。未来に見つかる脆弱性も含め、パッケージが少ないことはそれだけで管理するものを減らせます。

特定のパッケージに依存するパッケージはapt-cacheコマンドで調べられます。 不要なものはアンインストールしたいです。しかし、パッケージが他のパッケージに依存されている場合など、このような手段が取れない場合も多いです。

今回の脆弱性リストにあるもののうち、ローカル環境にインストールされている他のパッケージからはどのパッケージからも依存されていないパッケージを調べます。

実行結果

chromium is not depended on by any installed package.
libqt5sql5-sqlite is not depended on by any installed package.
linux-headers-rpi-2712 is not depended on by any installed package.
linux-headers-rpi-v8 is not depended on by any installed package.
linux-image-rpi-2712 is not depended on by any installed package.
linux-image-rpi-v8 is not depended on by any installed package.
login is not depended on by any installed package.
ncurses-bin is not depended on by any installed package.
p7zip is not depended on by any installed package.

依存されないパッケージは 9/197 パッケージしか該当しませんでした。 大半は依存パッケージ込みで不要な機能をごっそり削ることになるので、今後新しいパッケージの脆弱性が見つかった場合もシンプルにパッケージの要否を見極めることは難しいかもしれません。

必要なパッケージのアップデート

現在更新できるaptパッケージについてみてみます。

apt update

update実行ログの最後の行を見ると40パッケージの更新を行うことができるように見えます。

~~~
Fetched 1,689 kB in 3s (635 kB/s)
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
40 packages can be upgraded. Run 'apt list --upgradable' to see them.

実際にアップデートしてから脆弱性の数を測ってみると...

pi@rasoberrypi:~ $ sudo apt upgrade
~~実行結果は省略~~
pi@rasoberrypi:~ $ debsecan --suite bookworm --format simple | awk '{print $2}' | sort | uniq | wc -l
201

230件から201件に少しだけ減りましたが、脆弱性が0になるわけではないです。 Debian用のパッケージサーバーなどに比べ、Raspberry Pi OS 向けのサーバーに更新が反映までに期間が開くことが多く、反映される頃にはDebian側のパッケージで新たな脆弱性が報告されているので数がなかなか減らないです。 より最新のパッケージがリリースされているDebian用のパッケージサーバーからのインストールは、全てのパッケージでRaspberry Piで動く保証はないため、問答無用でアップデートをすることは避けた方が良いです。

そして水際の対策と予防へ

先ほどのGPTsにも聞いてみましょう。

GPTsとの会話の抜粋

会話全体: ChatGPT - CVE優先順位決定

事前に設定を練ることで、いざ問題が起こった時の被害を減らすことができそうです。

まとめ

世の中の便利そうなツールを使って、Raspberry Pi OS の脆弱性を分析しました。 便利なツールを使う過程で自身の理解度をうまく上げ、興味を持ち続けながら脆弱性をコントロールできると幸せなのだと思います。


株式会社フォトシンスでは、一緒にプロダクトを成長させる様々なレイヤのエンジニアを募集しています。 photosynth.co.jp

Akerunにご興味のある方はこちらから akerun.com

CIツールの活用によるチーム課題の改善

この記事は Akerun - Qiita Advent Calendar 2024 - Qiita の 12日目の記事です。

こんにちは、Miwa Akerun Technologiesが運営する賃貸住宅管理システムの開発チームでエンジニアをしている島田です。

このチームでは、CIツールにCircleCIを利用しています。

今回はCIの設定(.circleci/config.yml)を見ながら、これまでのチームの課題とその改善活動を振り返りたいと思います。

前提

CI設定の説明をする前に、アプリケーションの説明をしたいと思います。アプリケーションはSPAから利用されるREST APIです。

利用している技術スタックの概要は以下になります。

当初のCI設定

私がこのプロジェクトにジョインした当時、CIが実行していた内容はザックリと以下になります。

  • セットアップ
  • Lintを実行する
  • テストの実行とカバレッジの保存をする
  • CIが失敗した場合はSlackに通知をする
version: 2.1

orbs:
  go: circleci/go@1.11.0
  slack: circleci/slack@4.9.3

jobs:
  build:
    docker:
      - image: cimg/go:1.23.1-browsers
        environment:
          ...
      - image: cimg/mysql:8.0
        ...
    environment:
      ...
    steps:
      - checkout
      - go/mod-download-cached
      ...
      - run:
          name: setup tools
          command: |
            sudo apt update -y
            sudo apt install -y default-mysql-client
            ...
            go install -tags 'mysql' github.com/golang-migrate/migrate/v4/cmd/migrate@v4.18.1
            go install github.com/jstemmer/go-junit-report/v2@v2.1.0
      - run:
          name: "Setup Database"
          command: |
            mysql -h ${TEST_DATABASE_ENDPOINT} -P ${TEST_DATABASE_PORT} --protocol tcp -u ${TEST_DATABASE_USERNAME} -p${TEST_DATABASE_PASSWORD} < db/init-database.sql
            migrate -database="mysql://${TEST_DATABASE_USERNAME}:${TEST_DATABASE_PASSWORD}@tcp(${TEST_DATABASE_ENDPOINT}:${TEST_DATABASE_PORT})/${TEST_DATABASE_NAME}?x-tls-insecure-skip-verity=true" -path="db/migrate" up
      - run: golangci-lint run
      - run:
          name: create a coverage report
          command: |
            mkdir -p ./coverage
            go tool cover -html=./coverage/gotest.profile -o ./coverage/cover.html
      - store_artifacts:
          path: coverage
      - store_test_results:
          path: coverage
      ...

      - slack/notify:
          event: fail
          channel: XXXX
workflows:
  build-workflow:
    jobs:
      - build:
          context: slack-secrets

APIドキュメント

APIのドキュメントはOpenAPI Specification(OAS)のフォーマットで管理しています。

APIドキュメントの利用目的は、

  • API実装前の設計レビューのため
  • 他チーム(他社を含む)と連携をする際にI/Fを説明するため

改善前は、APIドキュメントを別なリポジトリで管理をしていました。

そしてローカルで確認するために、Redocly CLIを利用していました。

課題

以下のような課題がありました。

  • レビューの負荷:新規でAPIを実装する際に、設計のみをレビューをするには問題ありません。しかし既存APIの軽微な修正の場合にドキュメントと実装を別々にレビューするのはレビュアー・レビューイーともに負担がありました。またドキュメントのレビューをマージし忘れることも度々ありました。
  • 保守の負荷:リポジトリを管理する保守の負荷(Redoclyを利用するためのNodeのバージョン管理など)がありました。

改善案

まず、APIソースコードと同一リポジトリーにドキュメントを移行しました。

CIでRedocly CLIを実行して、実行結果のHTMLをビルドアーティファクトとして保存するようにしました。

ローカルでの確認時にNodeを必要としないように、VSCodeの拡張「Redocly OpenAPI」を利用して確認する運用にしました。

これにより以下の改善を実現する事が出来ました。

  • コードレビューの負担軽減
  • (ドキュメントの)マージ漏れがなくなる
  • (ドキュメント)リポジトリの保守コストがなくなった
version: 2.1
...
jobs:
  build:
...
  doc:
    docker:
      - image: cimg/node:22.11.0
    steps:
      - checkout
      - run:
          name: install dependencies
          command: npm i @redocly/cli@latest
      - run:
          name: generate docs
          command: |
            npx @redocly/cli build-docs doc/api/openapi/api.yml --output api.html --config=doc/api/openapi/redocly.yml
      - store_artifacts:
          path: api.html
          destination: docs/api
workflows:
  build-workflow:
    jobs:
      - build:
          context: slack-secrets
      - doc

ディレクトリ階層

doc
└── api
    ├── openapi
    │   ├── api.yml
    │   ├── components
    │   ├── paths
    │   └── redocly.yml

ER図

DDLの実行には、Go製のgolang-migrateというマイグレーションツールを利用しています。

課題

テーブルの全体像を知ることが出来ないという課題がありました。

Ruby on Railsであれば、schema.rb で全てのテーブル・インデックスを確認することができます。

しかし私たちのプロジェクトで利用しているmigrationツールでは、Up・Downの差分実行のコードしかないため全体像を確認する術がありませんでした。

もちろん、DBのクライアントツールを利用して開発環境などへ接続すれば確認することは可能です。ただ、スキーマ変更のDDLレビュー中などにクライアントツールを起動せずにサッと現状のスキーマや全体像を確認出来るようにしたいと考えていました。

改善

schemaspy を利用するようにしました。

schemaspyに決めた理由は、テーブル一覧・詳細、ER図の視認性が良い事とそれをHTMLで生成出来る事が決めてとなりました。

APIドキュメントと同様に、CIで実行して生成されたスキーマ定義に関するHTMLをビルドアーティファクトとして保存するようにしました。

これによって、簡単にDBのスキーマ構造が俯瞰的に確認することが出来るようになりました。

version: 2.1
...
jobs:
  build:
    docker:
     ...
    steps:
      - checkout
      - go/mod-download-cached
      ...
      - run:
          name: schemaspy
          command: |
            curl -L https://github.com/schemaspy/schemaspy/releases/download/v6.2.4/schemaspy-6.2.4.jar \
              --output ./schemaspy.jar
            curl -OJL https://repo1.maven.org/maven2/mysql/mysql-connector-java/8.0.30/mysql-connector-java-8.0.30.jar

            java -jar ./schemaspy.jar \
              -t mysql \
              -db ${TEST_DATABASE_NAME} \
              -host ${TEST_DATABASE_ENDPOINT} \
              -port ${TEST_DATABASE_PORT} \
              -u ${TEST_DATABASE_USERNAME} \
              -p ${TEST_DATABASE_PASSWORD} \
              -dp mysql-connector-java-8.0.30.jar \
              -s ${TEST_DATABASE_NAME} \
              -o output \
              -vizjs
      - store_artifacts:
          path: output
...

コードの複雑度

プロジェクト初期の設計から時間が経過して、機能が増えて徐々にコードベースが大きくなり、変更や拡張がやりにくい状態となってきました。

そのため柔軟に変更・拡張が出来るようにリファクタリングをすすめていくことになりました。

課題

リファクタリングを進めるにあたって、

  • どの箇所から対応していくかの優先度付けの問題
  • ビジネスサイドへの説明と作業進捗の可視化

という課題がありました。

改善

コードの複雑度を客観的に判断できて、修正難易度を可視化できるツールとしてCodeClimateの導入を決めました。

コードの複雑度を知るためには、CodeClimateとリポジトリを同期をするだけで可能になります。

CodeClimate上でコードカバレッジも管理したかったので、CIでカバレッジの情報をアップロードするようにしました。

コードの複雑度と変更頻度から、客観的にリファクタリング箇所の優先度を判断することが出来るようになりました。複雑度を数値化してくれるのでビジネスサイドへの説明もしやすくなりました。

また、カバレッジの一覧はGo純正のものよりも分かりやすいので大変重宝しています。

version: 2.1
...
jobs:
  build:
    ...
    steps:
      - checkout
      - go/mod-download-cached
     ...
      - run:
          name: Setup Code Climate test-reporter
          command: |
            curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
            chmod +x ./cc-test-reporter

            go install github.com/jandelgado/gcov2lcov@latest
      ...
      - run:
          name: create a coverage report
          command: |
            mkdir -p ./coverage
            go test -v -count=1 -coverprofile=./coverage/gotest.profile.tmp -p 1 -covermode=set ./... -coverpkg=./... 2>&1 | go-junit-report -set-exit-code=true > ./coverage/test-report-tmp.xml
            cat ./coverage/gotest.profile.tmp | grep -v "**/mocks/.*.go" > ./coverage/gotest.profile
            go tool cover -html=./coverage/gotest.profile -o ./coverage/cover.html

            gcov2lcov -infile=coverage/gotest.profile -outfile=coverage/coverage.lcov
      - store_artifacts:
          path: coverage
      - store_test_results:
          path: coverage
      ...
      - run:
          name: CodeClimate
          command: |
            ./cc-test-reporter before-build
            ./cc-test-reporter format-coverage -t lcov -o coverage/codeclimate.json coverage/coverage.lcov
            ./cc-test-reporter upload-coverage
...

まとめ

以上のように、APIドキュメント管理、ER図の生成、コードの複雑度の可視化など、CIの設定を通じてチームの課題解決を進めてきました。

これらの改善により少しづつですが着実に、開発効率の向上とコードの品質維持を実現することができています。

今後も継続的な改善を行い、より良い開発環境を目指していきたいと思います。


株式会社フォトシンスでは、一緒にプロダクトを成長させる様々なレイヤのエンジニアを募集しています。 photosynth.co.jp

Akerunにご興味のある方はこちらから akerun.com

コード規模感の把握とcloc

この記事は Akerun Advent Calendar 2024 - Qiita の11日目の記事です。

こんにちは、フォトシンス1年目の@ps-endoです。現在はモバイルアプリ(iOS)の開発を担当しています。 この記事ではモバイルアプリチームにジョインした際に実践したコード規模感の把握方法を1つ紹介します。

コードの規模感

コードの規模感を最初の段階で把握しておくと、実装の規模感やどこに処理が集中しているかなどがわかり、コード全体の把握を進め易くなります。 また、iOSアプリの場合はObjective-C, Objective-C++, Swiftを混ぜた状態で実装ができるため、各々がどのぐらいのボリュームであるのか確認しておきたい点です。

コードの規模感の確認はclocコマンドを使うとスムーズです。

github.com

cloc . --vcs=git --quiet

例としてSwiftのテストライブラリ swift-testing をclocで調べると、次の出力になります。

github.com/AlDanial/cloc v 2.02  T=0.30 s (854.3 files/s, 153086.0 lines/s)
-------------------------------------------------------------------------------
Language                     files          blank        comment           code
-------------------------------------------------------------------------------
Swift                          189           3809          12646          22178
Markdown                        37           1095            331           3800
CMake                           12             71            164            495
C++                              4             84            166            404
C/C++ Header                     8             98            285            270
Text                             2             45              0            189
XML                              2              0              0            166
YAML                             3              0              0             86
INI                              1              2              0              9
Dockerfile                       1              5              9              5
-------------------------------------------------------------------------------
SUM:                           259           5209          13601          27602
-------------------------------------------------------------------------------

全体の規模が判明したら、次は特定のプログラム言語に絞り、ソースコード毎のコード規模を取得します。

例えばSwiftに絞る場合は次のようなコマンドを実行します。

# cd ./Tests/TestingMacrosTests
cloc . --vcs=git --quiet --include-lang="Swift" --by-file

コードの行数が多いファイルから列挙してくれます。

github.com/AlDanial/cloc v 2.02  T=0.16 s (36.4 files/s, 6695.9 lines/s)
------------------------------------------------------------------------------------
File                                             blank        comment           code
------------------------------------------------------------------------------------
./TestDeclarationMacroTests.swift                   39             21            418
./ConditionMacroTests.swift                         25             24            339
./UniqueIdentifierTests.swift                       13              9             62
./TagMacroTests.swift                                6              9             57
./TestSupport/Parse.swift                            4              9             41
./TestSupport/FixIts.swift                           3             18              7
------------------------------------------------------------------------------------
SUM:                                                90             90            924
------------------------------------------------------------------------------------

上の結果から2つの情報が手に入ります

  • ファイルパスやファイル名で使っている単語
    • 単語から機能の関連性の推測
  • コード行数
    • コード行数だけでは判断は難しいですが処理が集中しているかどうかなど

余談ですが、clocコマンドは次のようにするとCSV形式で出力できますので、 そのCSVスプレッドシートなどに読み込み、どのあたりまで目を通したのか記録を残せるようチェックリストにします。

cloc . --vcs=git --quiet --include-lang="swift" --by-file --csv

ビューのファイル

iOSアプリの多くの画面はXibファイル、Storyboardファイル、UIKitもしくはSwiftUIのコードで作成されます。 ビューに関係するファイルをclocコマンドで列挙して規模感を把握していきます。

例えば、Storyboardファイルの一覧は次のコマンドで確認できます。

cloc . --vcs=git --include-ext="storyboard" --by-file

上のコマンドでStoryboardのファイルパスが手に入るので、Storyboardでカスタムクラスとして指定しているViewControllerをxmllintで取り出します。

xmllint --xpath "/document/scenes/scene/objects/viewController/@customClass" ./path/to/example.storyboard | awk -F'[ =\"]' '{print $4}'

次のような出力が得られます。(出力例であり、実際とは異なります)

ExampleListViewController
ExampleDetailViewController

画面を管理するViewControllerが得られたので、これもざっと見ていきます。

終わりに

上に紹介した方法で、ざっくりとコードの規模感やコードの中身を頭に入れておけば、「あの画面に関係するコードでちょっと聞きたくて」といった話に繋げやすくなるかなと思います。


株式会社フォトシンスでは、一緒にプロダクトを成長させる様々なレイヤのエンジニアを募集しています。 photosynth.co.jp

Akerunにご興味のある方はこちらから akerun.com

コードレビューの本質: 可読性のさきにあるもの

この記事は Akerun Advent Calendar 2024 - Qiita の10日目の記事です。

こんにちは。@ps-tsh です。バックエンドシステムの開発を担当しています。最近はエンジニアのマネジメントやプロダクトの仕様策定にも携わっており、自分でコードを書くよりもレビューの時間が多くなってきました。本当は一人で全部書きたいのですが、仕事を進める上でそういう我儘はいったん傍に置くことになります。というわけで、今回はコードレビューの話をしてみようと思います。

コードレビューは、ソフトウェア開発の現場で欠かせないプロセスの一つです。品質向上はもちろん、チーム全体の理解を深める絶好の機会でもあります。しかし、効果的なレビューを行うには「どこに時間をかけるべきか」を明確にすることが大切です。レビューアがすべての細部に同じ熱量で向き合うと、時間ばかりかかり、本当に重要な部分を見落としかねません。

今回は、コードレビューで「労力をかけるべき部分」と「自動化できる部分」を整理しつつ、レビューの本質的な目的に迫ります。また、コードの「読みやすさ」を客観的に高めるためのポイントと、長期的な視点での「メンテナンス性」の重要性についても掘り下げます。

労力をかけるべき部分、かけるべきでない部分

コードレビューでは、ついつい細かいスタイルやフォーマットの違いに目が向いてしまうことがあります。例えば、インデントが2スペースか4スペースか、あるいはコメントの位置が揃っているかどうかなど。しかし、こうしたスタイルのチェックは、自動化ツールに任せれば十分です。たとえば、Rubyのrubocopのようなツールを使えば、フォーマットの問題は機械的に検出・修正できます。人間が労力を費やすべきではありません。

一方で、人間が集中すべきなのは、「このコードは正しく動くだけでなく、将来にわたって読みやすく、変更しやすいか」という設計や構造の部分です。これこそ、機械では判断できない部分であり、レビューアの知見が生かされる領域です。

可読性の議論を超える:「認知負荷」を下げることが重要

コードレビューではよく「可読性が低い」「もっと読みやすくしてほしい」といったフィードバックが出ます。しかし、「可読性が高い」とは具体的にどういう状態を指すのか、人によって解釈が異なります。こうした主観的な指摘は、レビューの議論を迷走させがちです。

そこで注目したいのが、『A Philosophy of Software Design』という書籍で述べられている「認知負荷」という概念です。認知負荷とは、コードを理解するために頭を使う度合いのことです。この負荷が高いと、コードの全体像を把握するのに時間がかかり、バグや設計ミスが生まれやすくなります。逆に、認知負荷を下げる工夫をすれば、コードの意図が直感的に伝わり、レビューの質も向上します。

認知負荷を下げるための3つのアプローチ

適切なネーミング

まず、認知負荷を下げるために最も効果的な手段の一つが「適切なネーミング」です。関数や変数、クラスの名前がその役割や意図を的確に伝えていれば、コードを読んだ瞬間に「何をしているのか」が理解できます。一方、曖昧な名前や抽象的すぎる表現は、読み手の負担を増やします。

良いネーミングには、技術的な語彙力と一般的な表現力が求められます。日々のコミュニケーションやドキュメントの作成を通じて、適切な言葉を選ぶ練習をすることが大切です。

短く・少なく書く

コードを短く、少なく書くことも、認知負荷を下げる効果的な方法です。読む量が少なければ、それだけ全体を把握しやすくなります。同じロジックを繰り返し書かない、責務を明確に分けることでコードの凝集度を高めるといった工夫が重要です。

たとえば、一つの関数が複数の役割を持つと、その挙動を理解するために多くの前提知識が必要になります。凝集度を高めることで、各部分が独立して意味を成し、理解がスムーズになります。

予測可能性を高める

コードをすべて読まなくても全体像を把握できるようにするには、予測可能性を高めることが効果的です。法則性や対称性を意識して書かれたコードは、「こう書いてあれば、この部分もこうなっているはずだ」と予測できるため、読解にかかる時間を大幅に削減できます。

例えば、同じシステム内で同じ処理を行う箇所は、同じ書き方に統一することが大切です。また、広く使われているOSSライブラリを活用することで、「見慣れた設計」を取り入れるのも有効です。新しい独自の書き方を導入することは時に必要ですが、既知の方法を使える場面では、それを積極的に活用しましょう。

メンテナンス性を意識する

認知負荷を下げることに加え、コードレビューで忘れてはならないのが「メンテナンス性」です。書籍『Googleのソフトウェアエンジニアリング』には、「ソフトウェアエンジニアリングとは時間で積分したプログラミングである」との言葉があります。これは、コードの価値がその作成時点だけでなく、運用や保守の過程を通じて積み上がるという考え方を示しています。

自社サービスの開発では、リリースはゴールではなく、あくまで通過点です。リリース前のテスト通過は最低限の品質保証に過ぎず、本質的なコードの価値はその後の運用コストや変更容易性によって評価されます。コードがわかりやすく、柔軟であるほど、メンテナンスにかかるコストは下がります。

例えば、複雑に絡み合ったモジュールや、変更が広範囲に影響するような設計は、短期的には動作するかもしれませんが、長期的には大きな負債となります。レビューでは、コードが将来の変更や拡張に耐えられる設計になっているかを確認し、必要に応じて修正を提案することが重要です。

まとめ:レビューを通じて持続可能なコードを目指そう

コードレビューは、単に「動く」コードをチェックする場ではありません。そのコードが、チーム全体で理解しやすく、長期にわたって持続可能であるかを評価し、改善する場です。認知負荷を下げるための工夫、予測可能性を高めるための設計、そしてメンテナンス性を意識したレビューができれば、コードの価値を長く保つことができます。

運用コストを抑えつつ、チーム全体の生産性を高めるコードレビューを実践していきましょう。その先には、より良いプロダクトと、成長する開発チームが待っています。


株式会社フォトシンスでは、一緒にプロダクトを成長させる様々なレイヤのエンジニアを募集しています。 photosynth.co.jp

Akerunにご興味のある方はこちらから akerun.com

30分で shibboleth サーバを立てて GakuNin 連携の準備をする

この記事は Akerun - Qiita Advent Calendar 2024 - Qiita の 8 日目の記事です。

どうも daikw - Qiita です。

最近、学術認証フェデレーション(学認, GakuNin)を調べる機会があり、 Shibboleth を前提とした記述が多かったので、試しに Shibboleth サーバを建ててテストフェデレーション申請まで一通りやってみました。

以下の記事・仕様を参考にしました。

シボレス

シボレス - Wikipedia によると、

なお「シボレス」とは、ヘブライ語聖書の逸話に由来し、自国の特殊な発音を要するフレーズを読み上げさせる事で、相手が敵国人のスパイかどうかを判別するのに用いられた中世ヨーロッパの防諜技法の一種である。

SAMLフェデレーション環境下で、 Shibboleth は組織間の「信頼の合言葉」とも言える規約・メタデータを媒体に、互いのユーザーが正統なアクセス権を持つことを保証する役割を果たすので、良い命名ですね。

踏み絵と構造が似ているなと思いましたが、『識別対象が「言語的習慣」なのか「宗教的内面」なのかという点』で明確に違いますね。Fumie はサービス名にはできなそう。

構成

GCE インスタンス一つで完結するようにしました。

GIP を割り当て、ドメイン名を設定し、サーバ内で shibboleth サービスを動かします。 OS には Rocky Linux 8 を利用しました。

curl -O 'https://shibboleth.net/cgi-bin/sp_repo.cgi?platform=CentOS_7'
sudo cp sp_repo.cgi\?platform=* /etc/yum.repos.d/shibboleth.repo
sudo dnf install shibboleth httpd epel-release certbot python3-certbot-apache

# サービス起動
sudo systemctl start shibd
sudo systemctl enable shibd
sudo systemctl start httpd
sudo systemctl enable httpd

# よしなに編集して certbot で証明書発行
sudo vim /etc/httpd/conf.d/ssl.conf
sudo certbot --apache --email your-email@example.com

# shibboleth の設定を変更しリスタート
sudo vim /etc/shibboleth/shibboleth2.xml
sudo systemctl restart shibd

トラブルシュート

2つほど迷う挙動に見舞われました。 なお、 shibboleth サービスが起動していれば、ローカルからアクセスできるいくつかの管理用エンドポイントが利用できます。これでトラブルシュートができます。

# サーバの状態を確認
curl -k -s https://localhost/Shibboleth.sso/Status

# メタデータを確認
curl -k -s https://localhost/Shibboleth.sso/Metadata

# 有効なセッションを確認
curl -k -s https://localhost/Shibboleth.sso/Session

SELinux が弾いている

とりあえず shibboleth サービスの起動までは一息で行けましたが、何かで弾かれているようです。

[daikiwatanabe@shibboleth-sp ~]$ curl -k -s https://localhost/Shibboleth.sso/Status
<StatusHandler time='2024-11-11T04:36:16Z'><Version Xerces-C='3.3.0' XML-Tooling-C='3.3.0' Shibboleth='3.5.0'/><NonWindows sysname='Linux' nodename='shibboleth-sp' release='4.18.0-553.16.1.el8_10.cloud.0.1.x86_64' version='#1 SMP Thu Sep 12 13:56:18 UTC 2024' machine='x86_64'/><Status><Exception type='shibsp::ListenerException'>Cannot connect to shibd process, a site administrator should be notified that this web server has malfunctioned.</Exception></Status></StatusHandler>[daikiwatanabe@shibboleth-sp ~]$

よく調べてみると、 SELinuxshibd.sock へのアクセスを弾いていました。

[daikiwatanabe@shibboleth-sp ~]$ getenforce
Enforcing
[daikiwatanabe@shibboleth-sp ~]$ sudo ausearch -m avc | head
----
time->Mon Nov 11 01:25:30 2024
type=PROCTITLE msg=audit(1731288330.723:6915): proctitle=2F7573722F7362696E2F6874747064002D44464F524547524F554E44
type=SYSCALL msg=audit(1731288330.723:6915): arch=c000003e syscall=42 success=no exit=-13 a0=14 a1=7f1f31ff5ff0 a2=6e a3=39 items=0 ppid=86642 pid=87819 auid=4294967295 uid=48 gid=48 euid=48 suid=48 fsuid=48 egid=48 sgid=48 fsgid=48 tty=(none) ses=4294967295 comm="httpd" exe="/usr/sbin/httpd" subj=system_u:system_r:httpd_t:s0 key=(null)
type=AVC msg=audit(1731288330.723:6915): avc:  denied  { write } for  pid=87819 comm="httpd" name="shibd.sock" dev="tmpfs" ino=103453 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:var_run_t:s0 tclass=sock_file permissive=0
----
time->Mon Nov 11 01:25:32 2024
type=PROCTITLE msg=audit(1731288332.724:6916): proctitle=2F7573722F7362696E2F6874747064002D44464F524547524F554E44
type=SYSCALL msg=audit(1731288332.724:6916): arch=c000003e syscall=42 success=no exit=-13 a0=14 a1=7f1f31ff5ff0 a2=6e a3=39 items=0 ppid=86642 pid=87819 auid=4294967295 uid=48 gid=48 euid=48 suid=48 fsuid=48 egid=48 sgid=48 fsgid=48 tty=(none) ses=4294967295 comm="httpd" exe="/usr/sbin/httpd" subj=system_u:system_r:httpd_t:s0 key=(null)
type=AVC msg=audit(1731288332.724:6916): avc:  denied  { write } for  pid=87819 comm="httpd" name="shibd.sock" dev="tmpfs" ino=103453 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:var_run_t:s0 tclass=sock_file permissive=0

audit2allow を使ってポリシーを自動作成し、 semodule で適用します。

[daikiwatanabe@shibboleth-sp ~]$ sudo dnf -y install policycoreutils-python-utils
...
[daikiwatanabe@shibboleth-sp ~]$ sudo ausearch -c 'httpd' --raw | audit2allow -M httpd_shibd_policy
******************** IMPORTANT ***********************
To make this policy package active, execute:

semodule -i httpd_shibd_policy.pp

[daikiwatanabe@shibboleth-sp ~]$ sudo semodule -i httpd_shibd_policy.pp

動作するようになりました。

[daikiwatanabe@shibboleth-sp ~]$ curl -k -s https://localhost/Shibboleth.sso/Status
<StatusHandler time='2024-11-11T04:44:50Z'><Version Xerces-C='3.3.0' XML-Tooling-C='3.3.0' XML-Security-C='3.0.0' OpenSAML-C='3.3.0' Shibboleth='3.5.0'/><NonWindows sysname='Linux' nodename='shibboleth-sp' release='4.18.0-553.16.1.el8_10.cloud.0.1.x86_64'
...
</ds:X509Certificate></ds:X509Data></ds:KeyInfo></md:KeyDescriptor><md:KeyDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" use="encryption"><ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:KeyName>shibboleth-sp</ds:KeyName><ds:X509Data><ds:X509SubjectName>CN=shibboleth-sp</ds:X509SubjectName><ds:X509Certificate>
...
</ds:X509Certificate></ds:X509Data></ds:KeyInfo></md:KeyDescriptor><Status><OK/></Status></StatusHandler>

証明書の準備

証明書の設定に少し手こずりました。フェデレーション申請には証明書を添付する必要があります。

先ほど certbotHTTPS 用のサーバ証明書を発行しました。 また、shibboleth はインストール時に自己署名証明書を2つ生成します。

[daikiwatanabe@shibboleth-sp shibboleth]$ find /etc/shibboleth -regex '.*.pem'
/etc/shibboleth/sp-signing-key.pem
/etc/shibboleth/sp-signing-cert.pem
/etc/shibboleth/sp-encrypt-key.pem
/etc/shibboleth/sp-encrypt-cert.pem

が、フェデレーションの申請書には証明書の登録場所が一つだけ存在しています。どちらを登録するべきかマニュアル上は明確には分かりませんでした。以下の事実からも確定できず、とりあえず sigining でいいだろうと判断しました。どちらでもいいのかもしれません。

  • 生成される証明書の用途 ( use 要素) は encryption / sigining の2つがある
  • マニュアル 上は、「SAML 署名及び暗号化に使用する証明書」と記述がある

また、 Let’s Encrypt (certbot) のフルチェーン証明書を試しに入力すると、これも自己署名証明書と同様と判定されました。

自己署名証明書を含むメタデータを申請する場合、それをサーバ上で公開する必要があります。 IdP/SPで「パブリックでない証明書」(自己署名証明書)を利用する場合 - GakuNinShibInstall - meatwiki

/var/www/html ディレクトリを使って、 Apache のデフォルトの設定を利用して証明書を露出させます。

sudo mkdir -p /var/www/html/cert
sudo cp /etc/shibboleth/sp-signing-cert.pem /var/www/html/cert

こうすると https://shibsp.your.domain/cert/sp-signing-cert.pem で確認できるので、これを申請書に記載します。

なお、サービスで利用する証明書は TLS 証明書と分離しておくのが良いと考えます。なぜなら、TLS証明書のローテートとフェデレーションメタデータのローテートを同時に行うのは難しいからです。

その他、気がついたことがいくつかありました。

  • 生成したメタデータの中身を修正してフェデレーションに提出するのかと思いきや、申請書フォーマットがメタデータに対応していました。 shibboleth を利用している場合は二度手間になります。
  • shibboleth 以外の SAML 実装向けに、「テンプレート外メタデータ」という項目がありました。 shibboleth にこだわっているわけではないようです。

テストフェデレーション申請

準備したサーバの情報を使って申請したら、

申請画面

1日で承認されました。迅速!事務局の方ありがとうございました。

まとめ

  • タイトル通り 30 分で用意するのは結構大変ですが、1時間もあれば用意できると思います
  • 学生時代に触っていた GakuNin のログイン画面を運用している仕組みがさわれてよかったです

株式会社フォトシンスでは、一緒にプロダクトを成長させる様々なレイヤのエンジニアを募集しています。 hrmos.co

Akerun Pro の購入はこちらから akerun.com