【C言語】
mallocによるリスト構造と
Use-After-Freeのバグ

warning: use after ‘free’ of ‘p’

警告:メモリの解放後参照
[-Wanalyzer-use-after-free]


■use-after-freeのバグ例

void freeChain(Node* head) {
    // 順番に解放
    for(Node* p = head; p != NULL; p = p->next){
        free(p);
    }
}

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

■use-after-freeの修正例

void freeChain(Node* head) {
    Node *tmp;
    for(Node* p = head; p != NULL; p = tmp){
        tmp = p->next;
        free(p);
    }
}

■リスト構造(コンパイル可能)

#include <stdio.h>
#include <stdlib.h>
// ノードの構造体
typedef struct Node {
    int     data;
    struct  Node *next;
} Node ;
// 新しいノードを作成する関数
Node    *createNode(int data) {
    Node    *newNode = malloc(sizeof(Node));
    if(newNode == NULL){
        perror(NULL);
        exit(1);
    }
    newNode->data = data;
    newNode->next = NULL;
    return newNode;
}
// チェーンにノードを追加する関数
void appendNode(Node** head, int data) {
    Node* newNode = createNode(data);
    // チェーンが空の場合
    if (*head == NULL) {
        *head = newNode;
        return;
    }
    Node* currentNode = *head;
    // チェーンの末尾まで移動
    while (currentNode->next != NULL) {
        currentNode = currentNode->next;
    }
    // 新しいノードを末尾に追加
    currentNode->next = newNode;
}
// チェーンの要素を表示する関数
void printChain(Node* head) {
    // チェーンの要素を順番に表示
    for(Node* p = head; p != NULL; p = p->next){
        printf("%d\n", p->data);
    }
}
// チェーンの解放
#ifdef BUG
void freeChain(Node* head) {
    // 順番に解放
    for(Node* p = head; p != NULL; p = p->next){
        free(p);
    }
}
#else
void freeChain(Node* head) {
    Node *tmp;
    for(Node* p = head; p != NULL; p = tmp){
        tmp = p->next;
        free(p);
    }
}
#endif
int main(void) {
    // 空のチェーンを作成
    Node* head = NULL;
    // ノードを追加
    appendNode(&head, 1);
    appendNode(&head, 2);
    appendNode(&head, 3);    
    // チェーンの要素を表示
    printChain(head);
    // チェーンの解放
    freeChain(head);
}

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

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

参考:

CWE-416: Use After Free

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

MEM12-C. リソースの使用および解放の最中に発生するエラーが原因で関数を終了する場合に、Goto 連鎖の使用を検討する