【C言語】
Use after free
~チェーン構造の解放後参照を避ける~

warning: use after ‘free’ of ‘p’ [CWE-416]

警告:解放された後のメモリを使用

[-Wanalyzer-use-after-free]


■この記事の概要

この記事では、C言語における「Use After Free」エラーについて解説しています。

解放されたメモリを誤って再利用する典型的なバグの例を示し、それを修正するための安全なメモリ解放方法を具体的に説明しています。

gccのオプションを用いた静的および動的解析ツールも紹介しています。

■チェーン構造の基本的な使い方

#include <stdio.h>
#include <stdlib.h>
//ノードの構造体
typedef struct Node {
    char    *data;
    struct  Node *次;
} Node ; 
Node    *先頭 = NULL;   
Node    *新ノード作成(char *data) {
    Node    *新Node = malloc(sizeof(*新Node));
    if(新Node == NULL){
        perror(NULL);
        exit(1);
    }
    新Node->data    = data;
    新Node->次      = NULL;
    return 新Node;
}
void ノード追加(char *data) {
    Node* 新Node = 新ノード作成(data);
    // チェーンが空の場合
    if (先頭 == NULL) {
        先頭 = 新Node;
        return;
    }
    // チェーンの末尾まで移動
    Node* p ;
    for(p = 先頭; p->次 != NULL;p = p->次){
        ;
    }
    p->次 = 新Node;// 新しいノードを末尾に追加
}
void チェーン表示(void) {
    for(Node* p = 先頭; p != NULL; p = p->次){
        printf("ノードの内容[%s]\n", p->data);
    }
}
void チェーン解放(void) {
    Node *次を退避;
    for(Node* p = 先頭; p != NULL; p = 次を退避){
        次を退避 = p->次;
        free(p);
    }
}
int main(int argc,char *argv[]) {
    for(int i = 0 ; i < argc; i++){
        ノード追加(argv[i]);
    }
    チェーン表示();
    チェーン解放();
}
./a.out  1 23 456 7890
ノードの内容[./a.out]
ノードの内容[1]
ノードの内容[23]
ノードの内容[456]
ノードの内容[7890]

■解放後参照(Use After Free)の間違い

void チェーン解放(void) {
    for(Node* p = 先頭; p != NULL; p = p->次){
        free(p);
    }
}

チェーン構造のノード解放処理で
Use-After-Freeの問題がよく発生します。

free(p) で
p を解放した後に 
for 文第3式で 
p->次 を参照しているので
解放後参照のバグとなります。
このチェーン解放のバグは昔からよくある間違いで 
聖典 プログラミング言語C 7.8.5章でも紹介されています。


この問題は
gcc -fanalyzer
gcc -fsanitize=address
等で検出可能です。

■解放後参照(Use After Free)の修正例

void チェーン解放(void) {
    Node *次を退避;
    for(Node* p = 先頭; p != NULL; p = 次を退避){
        次を退避 = p->次;
        free(p);
    }
}

参考:

CWE-416: Use After Free

C-FAQ7.20: 動的に割り当てた記憶領域は解放した後には使えないね。

MEM30-C. 解放済みメモリにアクセスしない