xorl %eax, %eax

FreeBSD-SA-09:07: db(3) Information Leaks

leave a comment »

This was published on 22 April 2009 by FreeBSD project and credits go to Jaakko Heinonen, Xin LI. This vulnerability affects FreeBSD 6.0 up to 7.1 release. The bug is on a standard C library’s function. This routine db(3) is used as an interface to the Berkeley DB 1.85 database files. These database files include (on FreeBSD):
– /etc/pwd.db which provides access to /etc/passwd
– /etc/spwd.db which provides access to /etc/master.passwd
The following code is part of the FreeBSD 7.1 release:

151 /*
152  * We need a buffer for this page. Either allocate one, or evict a resident
153  * one (if we have as many buffers as we're allowed) and put this one in.
154  *
155  * If newbuf finds an error (returning NULL), it also sets errno.
156  */
157 static BUFHEAD *
158 newbuf(hashp, addr, prev_bp)
159         HTAB *hashp;
160         u_int32_t addr;
161         BUFHEAD *prev_bp;
162 {
163         BUFHEAD *bp;            /* The buffer we're going to use */
164         BUFHEAD *xbp;           /* Temp pointer */
      ...
176         if (hashp->nbufs || (bp->flags & BUF_PIN)) {
177                 /* Allocate a new one */
178                 if ((bp = (BUFHEAD *)malloc(sizeof(BUFHEAD)) == NULL)
179                         return (NULL);
180 #ifdef PURIFY
181                 memset(bp, 0xff, sizeof(BUFHEAD));
182 #endif
183                 if ((bp->page = (char *)malloc(hashp->BSIZE)) == NULL) {
184                         free(bp);
185                         return (NULL);
186                 }
187 #ifdef PURIFY
188                 memset(bp->page, 0xff, hashp->BSIZE);
189 #endif
     ...
284         return (bp);
285 }

This function can be found at lib/libc/db/hash/hash_buf.c and it’s used internally in db(3) code to allocate buffers for hash tables. At line 178 it uses malloc(3) to allocate memory of BUFHEAD bytes. This structure is defined at hash.h like this:

41 /* Buffer Management structures */
42 typedef struct _bufhead BUFHEAD;
43
44 struct _bufhead {
45         BUFHEAD         *prev;          /* LRU links */
46         BUFHEAD         *next;          /* LRU links */
47         BUFHEAD         *ovfl;          /* Overflow page buffer header */
48         u_int32_t        addr;          /* Address of this page */
49         char            *page;          /* Actual page data */
50         char            flags;
51 #define BUF_MOD         0x0001
52 #define BUF_DISK        0x0002
53 #define BUF_BUCKET      0x0004
54 #define BUF_PIN         0x0008
55 };

So.. it is a usual doubly linked list structure. As we all know malloc(3) does not clear the memory it allocates for efficiency. Here is the exact description from its man page:

The malloc() function allocates size bytes of uninitialized memory.  The
allocated space is suitably aligned (after possible pointer coercion) for
storage of any type of object.

However, if libc is compiled with PURIFY set then the allocated space will be initialized to 0xFF (lines 180-182). The same bug arises at line 183 and once again, PURIFY fixes it if defined (at lines 187-189). To fix this bug which can lead to information leak the following patch was applied:

         /* Allocate a new one */
-        if ((bp = (BUFHEAD *)malloc(sizeof(BUFHEAD))) == NULL)
+        if ((bp = (BUFHEAD *)calloc(1, sizeof(BUFHEAD))) == NULL)
             return (NULL);
 #ifdef PURIFY
         memset(bp, 0xff, sizeof(BUFHEAD));
 #endif
-        if ((bp->page = (char *)malloc(hashp->BSIZE)) == NULL) {
+        if ((bp->page = (char *)calloc(1, hashp->BSIZE)) == NULL) {
             free(bp);


Unlike malloc(3), calloc(3) initializes the allocated memory to zero before passing its pointer. In addition to this, the following was patched:

312 extern int
313 __buf_free(hashp, do_free, to_disk)
314         HTAB *hashp;
315         int do_free, to_disk;
316 {
317         BUFHEAD *bp;
      ...
330                 /* Check if we are freeing stuff */
331                 if (do_free)
332                    if (bp->page)
333                                 free(bp->page);
334                         BUF_REMOVE(bp);
335                         free(bp);
336                         bp = LRU;
      ...

This function is used to free buffers internally and can be found at lib/libc/db/hash/hash_buf.c. Here, even though bp->page is freed (line 333), its data never get cleared from memory which might also lead to information leak. This was patched like this:

         if (do_free) {
-            if (bp->page)
+            if (bp->page) {
+                (void)memset(bp->page, 0, hashp->BSIZE);
                 free(bp->page);
+            }
             BUF_REMOVE(bp);

Another similar information leak was present at lib/libc/db/btree/bt_split.c on the following routine:

324 /*
325  * BT_PAGE -- Split a non-root page of a btree.
326  *
327  * Parameters:
328  *      t:      tree
329  *      h:      root page
330  *      lp:     pointer to left page pointer
331  *      rp:     pointer to right page pointer
332  *      skip:   pointer to index to leave open
333  *      ilen:   insert length
334  *
335  * Returns:
336  *      Pointer to page in which to insert or NULL on error.
337  */
338 static PAGE *
339 bt_page(t, h, lp, rp, skip, ilen)
340         BTREE *t;
341         PAGE *h, **lp, **rp;
342         indx_t *skip;
343         size_t ilen;
344 {
       ...
383         /* Put the new left page for the split into place. */
384         if ((l = (PAGE *)malloc(t->bt_psize)) == NULL) {
385                 mpool_put(t->bt_mp, r, 0);
386                 return (NULL);
       ...
423         return (tp);
424 }


Where malloc(3) is used at line 384 and pointer l contains uninitialized memory. This was replaced with calloc(3) simply like that:

     /* Put the new left page for the split into place. */
-    if ((l = (PAGE *)malloc(t->bt_psize)) == NULL) {
+    if ((l = (PAGE *)calloc(1, t->bt_psize)) == NULL) {
         mpool_put(t->bt_mp, r, 0);

The last vulnerable dynamic memory allocation was at lib/libc/db/mpool/mpool.c where this code resides:

302 /*
303  * mpool_bkt
304  *      Get a page from the cache (or create one).
305  */
306 static BKT *
307 mpool_bkt(mp)
308         MPOOL *mp;
309 {
310         struct _hqh *head;
311         BKT *bp;
    ...
345
346 new:    if ((bp = (BKT *)malloc(sizeof(BKT) + mp->pagesize)) == NULL)
347                 return (NULL);
    ...
356         return (bp);
357 }


Where the malloc(3) call was replaced like this:

-new:    if ((bp = (BKT *)malloc(sizeof(BKT) + mp->pagesize)) == NULL)
+new:    if ((bp = (BKT *)calloc(1, sizeof(BKT) + mp->pagesize)) == NULL)
         return (NULL);
 #ifdef STATISTICS

Using the above information leaks a privileged application that uses db(3) might be able to either leak information to the user or inside the database files it manipulates. It won’t be hard to find whether this is possible or not after you have located applications using this library routine. For example, programs using getpwent(3) are also vulnerable since this routine is internally using dbopen() to open the password database file.

Written by xorl

April 23, 2009 at 14:21

Posted in bugs, freebsd

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s