NetBSD-Bugs archive
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]
lib/50681: memory leak in tdelete(3) when deleting root node
>Number: 50681
>Category: lib
>Synopsis: memory leak in tdelete(3) when deleting root node
>Confidential: no
>Severity: serious
>Priority: medium
>Responsible: lib-bug-people
>State: open
>Class: sw-bug
>Submitter-Id: net
>Arrival-Date: Wed Jan 20 08:35:00 +0000 2016
>Originator: Markiyan Kushnir
>Release:
>Organization:
private
>Environment:
>Description:
The FreeBSD's implementation of tsearch(3) family is almost literally based on NetBSDS's one. In NetBSD, the 1.4 revision of tdelete.c (http://cvsweb.netbsd.org/bsdweb.cgi/src/lib/libc/stdlib/tdelete.c?rev=1.4&content-type=text/x-cvsweb-markup&only_with_tag=MAIN) introduced a case: if the node to delete is the root node, it is not free()'ed by the library. Applications have to take care of it, which is not a good memory management pattern.
>How-To-Repeat:
I was able to reproduce it on a recent FreeBSD 11-CURRENT, which uses the NetBSD's implementation:
$ cat test-tdelete.c
#include <search.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
typedef int (*compar_t)(const void *, const void *);
int
main(void)
{
void *root, *node;
root = NULL;
printf("A=%p root=%p\n", tsearch("A", &root, (compar_t)strcmp), root);
printf("B=%p root=%p\n", tsearch("B", &root, (compar_t)strcmp), root);
node = tdelete("B", &root, (compar_t)strcmp);
printf("A=%p root=%p\n", node, root);
node = tdelete("A", &root, (compar_t)strcmp);
printf("B=%p root=%p\n", node, root);
}
$ cc test-tdelete.c
$ valgrind --leak-check=full ./a.out
==94266== Memcheck, a memory error detector
==94266== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==94266== Using Valgrind-3.10.0 and LibVEX; rerun with -h for copyright info
==94266== Command: ./a.out
==94266==
A=0x5400040 root=0x5400040
B=0x54010e0 root=0x5400040
A=0x5400040 root=0x5400040
B=0x5400040 root=0x0
==94266==
==94266== HEAP SUMMARY:
==94266== in use at exit: 4,120 bytes in 2 blocks
==94266== total heap usage: 3 allocs, 1 frees, 4,144 bytes allocated
==94266==
==94266== 24 bytes in 1 blocks are definitely lost in loss record 1 of 2
==94266== at 0x4C236C0: malloc (in /usr/local/lib/valgrind/vgpreload_memcheck-amd64-freebsd.so)
==94266== by 0x4E68434: tsearch (in /lib/libc.so.7)
==94266== by 0x40084C: main (in /tmp/a.out)
==94266==
==94266== LEAK SUMMARY:
==94266== definitely lost: 24 bytes in 1 blocks
==94266== indirectly lost: 0 bytes in 0 blocks
==94266== possibly lost: 0 bytes in 0 blocks
==94266== still reachable: 4,096 bytes in 1 blocks
==94266== suppressed: 0 bytes in 0 blocks
==94266== Reachable blocks (those to which a pointer was found) are not shown.
==94266== To see them, rerun with: --leak-check=full --show-leak-kinds=all
==94266==
==94266== For counts of detected and suppressed errors, rerun with: -v
==94266== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
>Fix:
The following fix:
- removes memory leak
- in the case if the node to delete is the root node, makes tdelete(3) return NULL, which I believe is better than the current behavior.
$ cat out.diff
--- /usr/src/lib/libc/stdlib/tdelete.c 2015-02-19 02:38:52.000000000 +0200
+++ /data0/mkushnir/eq/client-chroot/tmp/tdelete.c 2016-01-19 13:59:53.001365000 +0200
@@ -65,8 +65,10 @@
q->rlink = (*rootp)->rlink;
}
}
- if (p != *rootp)
- free(*rootp); /* D4: Free node */
+ if (p == *rootp) {
+ p = NULL;
+ }
+ free(*rootp); /* D4: Free node */
*rootp = q; /* link parent to new node */
return p;
}
Home |
Main Index |
Thread Index |
Old Index