Zephaniah Hull wrote:

> 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.

Thanks for catching this.  I think the security implications are
minimal, since it depends on the amount of memory someone has available.
Also, recovering from a swap file that you get from someone else is very
unusual.  But someone could make a swapfile that causes Vim to crash.

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

Effectively means that this block will never be reused, since the size
doesn't match the page size.

> 2: Add a block_hdr->bh_page_size.

Same.

> 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.

Works, but wastes a bit of memory.

Besides the solutions you suggest, it's also possible to reallocate the
memory when the page size is changed.  I think that is a cleaner
solution, because it's local.

I think it's also a good idea to check that the page size as specified
in block 0 is at least the minimal block size.

Have a look at this patch:

*** ../../vim-7.0.224/src/memline.c     Tue Mar  6 20:27:03 2007
--- memline.c   Thu Apr 19 16:10:39 2007
***************
*** 1015,1032 ****
--- 1015,1053 ----
        msg_end();
        goto theend;
      }
+ 
      /*
       * If we guessed the wrong page size, we have to recalculate the
       * highest block number in the file.
       */
      if (mfp->mf_page_size != (unsigned)char_to_long(b0p->b0_page_size))
      {
+       unsigned previous_page_size = mfp->mf_page_size;
+ 
        mf_new_page_size(mfp, (unsigned)char_to_long(b0p->b0_page_size));
+       if (mfp->mf_page_size < previous_page_size)
+       {
+           msg_start();
+           msg_outtrans_attr(mfp->mf_fname, attr | MSG_HIST);
+           MSG_PUTS_ATTR(_(" has been damaged (page size is smaller than 
minimum value).\n"),
+                       attr | MSG_HIST);
+           msg_end();
+           goto theend;
+       }
        if ((size = lseek(mfp->mf_fd, (off_t)0L, SEEK_END)) <= 0)
            mfp->mf_blocknr_max = 0;        /* no file or empty file */
        else
            mfp->mf_blocknr_max = (blocknr_T)(size / mfp->mf_page_size);
        mfp->mf_infile_count = mfp->mf_blocknr_max;
+ 
+       /* need to reallocate the memory used to store the data */
+       p = alloc(mfp->mf_page_size);
+       if (p == NULL)
+           goto theend;
+       mch_memmove(p, hp->bh_data, previous_page_size);
+       vim_free(hp->bh_data);
+       hp->bh_data = p;
+       b0p = (ZERO_BL *)(hp->bh_data);
      }
  
  /*
***************
*** 2549,2555 ****
                if (lineadd)
                {
                    --(buf->b_ml.ml_stack_top);
!                       /* fix line count for rest of blocks in the stack */
                    ml_lineadd(buf, lineadd);
                                                        /* fix stack itself */
                    buf->b_ml.ml_stack[buf->b_ml.ml_stack_top].ip_high +=
--- 2570,2576 ----
                if (lineadd)
                {
                    --(buf->b_ml.ml_stack_top);
!                   /* fix line count for rest of blocks in the stack */
                    ml_lineadd(buf, lineadd);
                                                        /* fix stack itself */
                    buf->b_ml.ml_stack[buf->b_ml.ml_stack_top].ip_high +=
***************
*** 2852,2863 ****
                mf_put(mfp, hp, TRUE, FALSE);
  
                buf->b_ml.ml_stack_top = stack_idx;     /* truncate stack */
!                   /* fix line count for rest of blocks in the stack */
!               if (buf->b_ml.ml_locked_lineadd)
                {
                    ml_lineadd(buf, buf->b_ml.ml_locked_lineadd);
                    buf->b_ml.ml_stack[buf->b_ml.ml_stack_top].ip_high +=
!                                               buf->b_ml.ml_locked_lineadd;
                }
                ++(buf->b_ml.ml_stack_top);
  
--- 2873,2884 ----
                mf_put(mfp, hp, TRUE, FALSE);
  
                buf->b_ml.ml_stack_top = stack_idx;     /* truncate stack */
!               /* fix line count for rest of blocks in the stack */
!               if (buf->b_ml.ml_locked_lineadd != 0)
                {
                    ml_lineadd(buf, buf->b_ml.ml_locked_lineadd);
                    buf->b_ml.ml_stack[buf->b_ml.ml_stack_top].ip_high +=
!                                                 buf->b_ml.ml_locked_lineadd;
                }
                ++(buf->b_ml.ml_stack_top);
  
***************
*** 3187,3193 ****
   * The stack is updated to lead to the locked block. The ip_high field in
   * the stack is updated to reflect the last line in the block AFTER the
   * insert or delete, also if the pointer block has not been updated yet. But
!  * if if ml_locked != NULL ml_locked_lineadd must be added to ip_high.
   *
   * return: NULL for failure, pointer to block header otherwise
   */
--- 3208,3214 ----
   * The stack is updated to lead to the locked block. The ip_high field in
   * the stack is updated to reflect the last line in the block AFTER the
   * insert or delete, also if the pointer block has not been updated yet. But
!  * if ml_locked != NULL ml_locked_lineadd must be added to ip_high.
   *
   * return: NULL for failure, pointer to block header otherwise
   */
***************
*** 3244,3254 ****
                                            buf->b_ml.ml_flags & ML_LOCKED_POS);
        buf->b_ml.ml_locked = NULL;
  
!           /*
!            * if lines have been added or deleted in the locked block, need to
!            * update the line count in pointer blocks
!            */
!       if (buf->b_ml.ml_locked_lineadd)
            ml_lineadd(buf, buf->b_ml.ml_locked_lineadd);
      }
  
--- 3265,3275 ----
                                            buf->b_ml.ml_flags & ML_LOCKED_POS);
        buf->b_ml.ml_locked = NULL;
  
!       /*
!        * If lines have been added or deleted in the locked block, need to
!        * update the line count in pointer blocks.
!        */
!       if (buf->b_ml.ml_locked_lineadd != 0)
            ml_lineadd(buf, buf->b_ml.ml_locked_lineadd);
      }
  


-- 
If you had to identify, in one word, the reason why the
human race has not achieved, and never will achieve, its
full potential, that word would be "meetings."

 /// Bram Moolenaar -- [EMAIL PROTECTED] -- http://www.Moolenaar.net   \\\
///        sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\
\\\        download, build and distribute -- http://www.A-A-P.org        ///
 \\\            help me help AIDS victims -- http://ICCF-Holland.org    ///

Reply via email to