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