【C言語】
Use After Freeエラーを防ぐ方法
-メモリ解放後使用-

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

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

[-Wanalyzer-use-after-free]


■この記事の概要

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

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

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

■チェーンの解放処理で皆が間違うUse After 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);
    }
}

■チェーンの使い方の例(BUGの修正例)

#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. 解放済みメモリにアクセスしない