double free — DedSec Project
Double free — μοναδική ιδιοκτησία και σωστή εκκαθάριση Τι σημαίνει η αποτυχία Η δεύτερη κλήση free στην ίδια δέσμευση μνήμης αποτελεί μη ορισμένη συμπεριφορά, ακόμη και αν το πρόγραμμα…
Double free — μοναδική ιδιοκτησία και σωστή εκκαθάριση
Τι σημαίνει η αποτυχία
Η δεύτερη κλήση free στην ίδια δέσμευση μνήμης αποτελεί μη ορισμένη συμπεριφορά, ακόμη και αν το πρόγραμμα φαίνεται να συνεχίζει κανονικά. Συχνά δύο διαδρομές καθαρισμού θεωρούν ότι κατέχουν το ίδιο αντικείμενο: η μία το ελευθερώνει και η άλλη κρατά δείκτη προς την παλιά θέση. Ο allocator ή ο AddressSanitizer μπορεί να εμφανίσει διάγνωση διπλής αποδέσμευσης, αλλά το ακριβές μήνυμα διαφέρει ανά περιβάλλον.
Ελεγχόμενη λύση
Καθόρισε έναν υπεύθυνο για κάθε δέσμευση. Η βοηθητική συνάρτηση επιστρέφει νέο buffer μέσω δείκτη εξόδου. Μόνο μετά την επιτυχία αποκτά ο καλών την ιδιοκτησία και υποχρεούται να το απελευθερώσει μία φορά. Σε αποτυχία επιστρέφεται κωδικός και ο δείκτης εξόδου μένει NULL. Το παράδειγμα είναι ασφαλής δοκιμή χωρίς σκόπιμη αλλοίωση μνήμης.
Παράδειγμα κώδικα
Αποθήκευσε ως double_free_lab.c.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static int make_label(char **out, const char *source) {
if (!out || !source) return -1;
*out = NULL;
size_t n = strlen(source);
if (n > 64) return -2;
char *p = malloc(n + 1);
if (!p) return -3;
memcpy(p, source, n + 1);
*out = p; /* caller now owns p */
return 0;
}
int main(void) {
char *owner = NULL;
if (make_label(&owner, "safe") != 0) return 1;
if (strcmp(owner, "safe") != 0) return 2;
free(owner);
owner = NULL;
free(owner); /* C defines free(NULL) as a no-op */
if (make_label(&owner, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789too-big") != -2 || owner != NULL) return 3;
puts("PASS: single owner, cleanup, error path");
}
Γιατί λειτουργεί
Η μεταφορά ιδιοκτησίας γίνεται μόνο αφού τελειώσει η αντιγραφή. Η free(NULL) επιτρέπεται στην C, αλλά η μετατροπή μιας μεταβλητής σε NULL δεν προστατεύει άλλα αντίγραφα του ίδιου δείκτη. Η συνάρτηση προϋποθέτει έγκυρη συμβολοσειρά C που τερματίζεται με NUL. Μη έμπιστα δεδομένα χωρίς τελικό NUL απαιτούν διαφορετική διεπαφή με δηλωμένο μήκος. Τεκμηρίωσε ποιος ελευθερώνει τι, ειδικά σε πρόωρες επιστροφές.
Ασφαλής επαλήθευση
Μεταγλώττισε με gcc -std=c11 -Wall -Wextra -Werror -fsanitize=address,undefined -fno-omit-frame-pointer -g double_free_lab.c -o double_free_lab && ./double_free_lab. Αν δεν υπάρχει sanitizer, εκτέλεσε με αυστηρές προειδοποιήσεις και δήλωσε ότι δεν έγινε έλεγχος με sanitizer. Δεν χρειάζεται επικίνδυνη εκτέλεση που καλεί free δύο φορές σε κανονικό αντικείμενο.
Άσκηση και αποδεικτικά
Σημείωσε τον ιδιοκτήτη πριν και μετά την επιτυχία, προκάλεσε απόρριψη υπερβολικά μεγάλου ονόματος και εξήγησε την εκκαθάριση κάθε περίπτωσης. Σχεδίασε δύο alias προς την ίδια δέσμευση και αιτιολόγησε γιατί δεν αρκεί να μηδενίσεις το ένα. Σε C++ χρησιμοποίησε RAII. Ο sanitizer δεν αποδεικνύει ορθότητα για μη εκτελεσμένες διαδρομές.
Πρωτογενής τεκμηρίωση: https://clang.llvm.org/docs/AddressSanitizer.html