I tried posting about this a few days ago, but the message vanished
without a bounce.

Since then I noticed that it has security implications as well.

This took a while to find, and I'll put crash reproduction directions at
the bottom.  But first a description of what's broken, why it's broken,
and some ways to fix it.


The bug, simply put, is that if we ever try to recover a swapfile that
has a page size larger then MIN_SWAP_PAGE_SIZE[0], and for any reason
mf_recover decides that we've used enough memory and should try to reuse
some pages[1], we try to read a swapfile page size chunk into a
MIN_SWAP_PAGE_SIZE buffer[2].

Since the rules on mf_recover are quite possible to determine from
source and knowledge of the system[3], and the swapfile defines it's own
page size[4], this allows anyone who can trick someone into recovering a
specially crafted swapfile to cause them to read an arbitrary number of
attacker controlled bytes into a MIN_SWAP_PAGE_SIZE buffer[2].  Which
looks like a potential security hole to me. (Even if it is bloody hard
to setup the conditions for it.)


I can think of three fixes for this, 1 is good if we plan on allowing
page_size changes, 2 I'm not sure I can think of a justification for, 3
is non-invasive, easy, and there's a fully tested patch attached that
implements it, it's also a kludge, but.


Zephaniah E. Hull.


1: Replace block_hdr->bh_page_count with bh_size, always check against
that.

2: Add a block_hdr->bh_page_size.

3: In ml_recover don't do a mf_put on the initial header block until we
are done parsing the swapfile in it's entirety.



0: MIN_SWAP_PAGE_SIZE is defined to be 1024 bytes.  ml_recover in
memline.c has the following comment and line:
---
    /*
     * The page size set in mf_open() might be different from the page size
     * used in the swap file, we must get it from block 0.  But to read block
     * 0 we need a page size.  Use the minimal size for block 0 here, it will
     * be set to the real value below.
     */
    mfp->mf_page_size = MIN_SWAP_PAGE_SIZE;
---

1:
mf_release in memfile.c has the following:
---
/*
 * Need to release a block if the number of blocks for this memfile is
 * higher than the maximum or total memory used is over 'maxmemtot'
 */
need_release = ((mfp->mf_used_count >= mfp->mf_used_count_max)
                              || (total_mem_used >> 10) >= (long_u)p_mmt);
---
On the system where this showed up, p_mmt ended up as 62, however a
fairly large swapfile should be able to trigger this as well on most
systems.

2:
First, mf_release checks to see if the released page has a buffer of the
right size, but only by checking page_count, since page_count is
identical, this falsely assumes that the buffer is of the proper size.
mf_release in memfile.c
---
    /*
     * If a bhdr_T is returned, make sure that the page_count of bh_data is
     * right
     */
    if (hp->bh_page_count != page_count)
    { ...  }
    return hp;
---
Then when that gets returned, fairly quickly it's passed to mf_read
in memfile.c which, well:
---
    page_size = mfp->mf_page_size;
    offset = (off_t)page_size * hp->bh_bnum;
    size = page_size * hp->bh_page_count;
...
    if ((unsigned)vim_read(mfp->mf_fd, hp->bh_data, size) != size)
---
And, boom.


3: See option.c for the maxmemtot and maxmem variable defaults, it
wanders through platform specific code, but it's not that hard to get a
real good guess at what the values will be.

4: Mentioned in the comment of [0], ml_recover pulls the page size out
of the swapfile header as a signed 32bit long.

-- 
          1024D/E65A7801 Zephaniah E. Hull <[EMAIL PROTECTED]>
           92ED 94E4 B1E6 3624 226D  5727 4453 008B E65A 7801
            CCs of replies from mailing lists are requested.

lba32 requires bios support - which is common, and working bios support
which is slightly less common on older boxes
  -- Alan Cox
diff -ur tmp/vim-7.0/vim/src/memline.c vim-7.0/vim/src/memline.c
--- tmp/vim-7.0/vim/src/memline.c	2007-04-16 08:34:01.000000000 -0400
+++ vim-7.0/vim/src/memline.c	2007-04-16 08:00:58.000000000 -0400
@@ -836,7 +836,7 @@
     buf_T	*buf = NULL;
     memfile_T	*mfp = NULL;
     char_u	*fname;
-    bhdr_T	*hp = NULL;
+    bhdr_T	*hp = NULL, *hp0 = NULL;
     ZERO_BL	*b0p;
     int		b0_ff;
     char_u	*b0_fenc = NULL;
@@ -968,7 +968,7 @@
 /*
  * try to read block 0
  */
-    if ((hp = mf_get(mfp, (blocknr_T)0, 1)) == NULL)
+    if ((hp0 = mf_get(mfp, (blocknr_T)0, 1)) == NULL)
     {
 	msg_start();
 	MSG_PUTS_ATTR(_("Unable to read block 0 from "), attr | MSG_HIST);
@@ -979,7 +979,7 @@
 	msg_end();
 	goto theend;
     }
-    b0p = (ZERO_BL *)(hp->bh_data);
+    b0p = (ZERO_BL *)(hp0->bh_data);
     if (STRNCMP(b0p->b0_version, "VIM 3.0", 7) == 0)
     {
 	msg_start();
@@ -1073,9 +1073,6 @@
 	b0_fenc = vim_strnsave(p, (int)(b0p->b0_fname + B0_FNAME_SIZE - p));
     }
 
-    mf_put(mfp, hp, FALSE, FALSE);	/* release block 0 */
-    hp = NULL;
-
     /*
      * Now that we are sure that the file is going to be recovered, clear the
      * contents of the current buffer.
@@ -1327,6 +1324,16 @@
     {
 	if (hp != NULL)
 	    mf_put(mfp, hp, FALSE, FALSE);
+
+	/*
+	 * XXX: Due to potential page size changes, this MUST not be done while
+	 * we are still parsing the swapfile.
+	 */
+
+	if (hp0 != NULL)
+	    mf_put(mfp, hp0, FALSE, FALSE);	/* release block 0 */
+	hp0 = NULL;
+
 	mf_close(mfp, FALSE);	    /* will also vim_free(mfp->mf_fname) */
     }
     vim_free(buf);

Attachment: signature.asc
Description: Digital signature

Reply via email to