【C言語】
解放後のメモリ参照
(Use After Free)

Use After Free

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

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


■チェーンの解放処理で皆が間違うfree関数の使い方

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

Use After Freeの代表的な間違いの例です。

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: 動的に割り当てた記憶領域は解放した後には使えないね。

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