組み込みシステムやリアルタイムシステムでは、ソフトウェアの障害は単なる技術的な不具合ではありません。身体的な傷害、壊滅的なデータ侵害、あるいはミッションの完全な喪失につながる可能性があります。C++は開発者にハードウェアレジスタやポインタへの低レベルアクセスを提供するため、未定義動作、メモリ破壊、巧妙なセキュリティ脆弱性を持ち込む機会も十分に提供してしまいます。
これらのリスクを排除するため、エンジニアリングリーダーや安全 critical な業界は MISRA C++ に依存しています。
MISRA C++とは?
MISRA(Motor Industry Software Reliability Association)は、安全 critical な車載ECUにおいて安全で信頼性が高く予測可能なソフトウェア実行を保証するために、CおよびC++のコーディング標準を開発しました。今日、MISRA C++は危険な言語機能、曖昧な構文、未定義動作を取り除き、C++を厳格にサブセット化するための国際的なゴールドスタンダードとなっています。
単純なフォーマットガイドとしてではなく、MISRAルールはソフトウェアバグの根本原因を対象としています:
- メモリ安全性: バッファオーバーフロー、use-after-free、スタック破壊の排除。
- 決定論的実行: 動的ヒープの断片化や未処理の例外の防止。
- 型安全性: 暗黙の切り捨てや危険な暗黙的キャストの阻止。
MISRA準拠が必須となる業界は?
MISRA C++準拠は、ミッション critical で高度に規制された幅広い分野で義務付けられています:
- 自動車(ISO 26262): OEMやTier-1サプライヤー(例:Stellantis、BMW、Bosch)がエンジン制御ユニット、ADAS認識、電子ブレーキ、ソフトウェア定義車両(SDV)アーキテクチャに採用。
- 航空宇宙・アビオニクス(DO-178C): 飛行制御コンピュータ、衛星姿勢決定システム、ロケット誘導モジュール。
- 医療機器(IEC 62304): 自動インスリンポンプ、ペースメーカー、手術ロボット、人工呼吸器。
- 鉄道システム(EN 50128)および防衛: 信号ネットワーク、自律防衛プラットフォーム、重工業オートメーション。
セキュリティ重要なMISRA C++ルール トップ10
以下に、組み込みデバイスの低レベル脆弱性を防ぐための、非準拠の例と安全なモダンC++による修正を含む、セキュリティ重要度の高いMISRA C++原則10項目を紹介します。
1. ポインタ演算を制限し、境界の安全性を確保する
セキュリティ上の脅威: バッファオーバーフロー、ポインタ操作の悪用、境界外メモリ破壊。
❌ 非準拠:
void processBuffer(uint8_t *ptr, size_t len) {
for (size_t i = 0; i <= len; ++i) { // Bug: <= allows out-of-bounds write
*(ptr + i) = 0;
}
}
✅ 準拠した修正:
#include <span>
void processBuffer(std::span<uint8_t> buffer) {
for (auto &val : buffer) {
val = 0; // Bounds-safe container iteration
}
}
2. すべての変数は使用前に初期化すること
セキュリティ上の脅威: 情報漏洩、未定義動作、未初期化メモリの悪用。
❌ 非準拠:
void authenticateUser() {
bool isAuthenticated; // Uninitialized stack memory containing garbage data
if (checkToken()) {
isAuthenticated = true;
}
if (isAuthenticated) { // Undefined behavior if checkToken() returns false
grantAccess();
}
}
✅ 準拠した修正:
void authenticateUser() {
bool isAuthenticated = false; // Explicit default initialization
if (checkToken()) {
isAuthenticated = true;
}
if (isAuthenticated) {
grantAccess();
}
}
3. 動的ヒープメモリ割り当ては制限または回避すること
セキュリティ上の脅威: ヒープ枯渇によるDoS(サービス拒否)、メモリリーク、非決定論的な割り当て遅延。
❌ 非準拠:
void handleMessage(const char *data, size_t length) {
char *buffer = new char[length]; // Dangerous heap allocation per message
// process buffer...
delete[] buffer;
}
✅ 準拠した修正:
#include <array>
void handleMessage(const char *data, size_t length) {
std::array<char, 256> staticBuffer; // Stack-allocated or statically pooled
if (length < staticBuffer.size()) {
// process safely without dynamic heap calls
}
}
4. データ損失を引き起こす暗黙的な型変換を行わない
セキュリティ上の脅威: 整数の切り捨て、ラップアラウンドエラー、バッファオーバーフローの悪用。
❌ 非準拠:
void parseHeader(uint32_t declaredLength) {
uint8_t bufferSize = declaredLength; // Implicit narrowing: 512 truncated to 0!
uint8_t *dest = new uint8_t[bufferSize];
}
✅ 準拠した修正:
void parseHeader(uint32_t declaredLength) {
if (declaredLength > 255) {
return; // Guard against truncation and invalid input
}
uint8_t bufferSize = static_cast<uint8_t>(declaredLength);
}
5. volatileデータアクセスを正しく処理すること
セキュリティ上の脅威: コンパイラ最適化によるレジスタキャッシングに起因する競合状態、ハードウェアイベントの見落とし、セキュリティチェックの回避。
❌ 非準拠:
uint32_t *const AuthStatusReg = reinterpret_cast<uint32_t*>(0x40001000);
void verifySecurityGate() {
if (*AuthStatusReg == 0x1) { // Compiler may cache read value in a CPU register
unlockDevice();
}
}
✅ 準拠した修正:
volatile uint32_t *const AuthStatusReg = reinterpret_cast<volatile uint32_t*>(0x40001000);
void verifySecurityGate() {
if (*AuthStatusReg == 0x1) { // Forces hardware re-read on every execution
unlockDevice();
}
}
6. ビット演算における未定義動作を回避する
セキュリティ上の脅威: コンパイラ最適化の盲点、シフトレジスタのオーバーフロー、ロジックの破壊。
❌ 非準拠:
uint32_t shiftRegister(uint32_t val, int bits) {
return val << bits; // Undefined behavior if bits < 0 or bits >= 32
}
✅ 準拠した修正:
uint32_t shiftRegister(uint32_t val, int bits) {
if (bits < 0 || bits >= 32) {
return 0; // Explicit bounds checking to prevent UB
}
return val << bits;
}
7. constの正当性を厳格に適用する
セキュリティ上の脅威: 静的な設定パラメータの不正な変更による偶発的なメモリ改ざんや権限昇格。
❌ 非準拠:
struct SystemConfig {
uint32_t key;
};
void updateConfig(SystemConfig &cfg) {
cfg.key = 0xDEADBEEF; // Mutable access can be exploited or mutated by mistake
}
✅ 準拠した修正:
struct SystemConfig {
uint32_t key;
};
void inspectConfig(const SystemConfig &cfg) {
// cfg.key = ...; -> Compiler error: prevents unauthorized mutation
}
8. 変数のシャドウイング(名前の隠蔽)を回避する
セキュリティ上の脅威: 意図したグローバル状態ではなくローカルスコープの変数を参照することによるロジックの混乱と認可バイパス。
❌ 非準拠:
int globalAccessLevel = 0;
void processLogin(int accessLevel) {
int globalAccessLevel = 1; // Shadows global variable!
// Developer assumes global state is updated, but it remains unchanged.
}
✅ 準拠した修正:
int g_AccessLevel = 0;
void processLogin(int inputAccessLevel) {
g_AccessLevel = inputAccessLevel; // Clear, distinct variable scopes
}
9. 「マジックナンバー」の使用を避ける
セキュリティ上の脅威: ハードコードされたロジックの欠陥、保守不能なセキュリティポリシー、監査の失敗。
❌ 非準拠:
void validateUser(uint8_t role) {
if (role == 3) { // What is 3? Admin? Guest? Superuser?
grantSuperUserAccess();
}
}
✅ 準拠した修正:
enum class UserRole : uint8_t {
Guest = 1,
Operator = 2,
Administrator = 3
};
void validateUser(UserRole role) {
if (role == UserRole::Administrator) {
grantSuperUserAccess();
}
}
10. switch文にはdefault句を必ず付けること
セキュリティ上の脅威: 破損したハードウェアenum値を処理する際に安全チェックをスキップする未処理の状態遷移。
❌ 非準拠:
void handleCommand(ProtocolState state) {
switch (state) {
case ProtocolState::Idle: resetTimer(); break;
case ProtocolState::Active: processData(); break;
// Missing default: corrupted enum skips checks silently!
}
}
✅ 準拠した修正:
void handleCommand(ProtocolState state) {
switch (state) {
case ProtocolState::Idle: resetTimer(); break;
case ProtocolState::Active: processData(); break;
default:
triggerSecurityAlarm(); // Catches corrupt or unexpected states safely
break;
}
}
まとめ
自動化されたCI/CD静的解析パイプライン(CppDependなど)でMISRA C++ルールを採用することで、コードが物理ハードウェアに到達するはるか前に、コミットごとに安全性とセキュリティが検証されることが保証されます。

