test_bson_append_code_with_scope (void)
{
   const uint8_t *scope_buf = NULL;
   uint32_t scopelen = 0;
   uint32_t len = 0;
   bson_iter_t iter;
   bool r;
   const char *code = NULL;
   bson_t *b;
   bson_t *b2;
   bson_t *scope;

   /* Test with NULL bson, which converts to just CODE type. */
   b = bson_new ();
   BSON_ASSERT (
      bson_append_code_with_scope (b, "code", -1, "var a = {};", NULL));
   b2 = get_bson ("test30.bson");
   BSON_ASSERT_BSON_EQUAL (b, b2);
   r = bson_iter_init_find (&iter, b, "code");
   BSON_ASSERT (r);
   BSON_ASSERT (BSON_ITER_HOLDS_CODE (&iter)); /* Not codewscope */
   bson_destroy (b);
   bson_destroy (b2);

   /* Empty scope is still CODEWSCOPE. */
   b = bson_new ();
   scope = bson_new ();
   BSON_ASSERT (
      bson_append_code_with_scope (b, "code", -1, "var a = {};", scope));
   b2 = get_bson ("code_w_empty_scope.bson");
   BSON_ASSERT_BSON_EQUAL (b, b2);
   r = bson_iter_init_find (&iter, b, "code");
   BSON_ASSERT (r);
   BSON_ASSERT (BSON_ITER_HOLDS_CODEWSCOPE (&iter));
   bson_destroy (b);
   bson_destroy (b2);
   bson_destroy (scope);

   /* Test with non-empty scope */
   b = bson_new ();
   scope = bson_new ();
   BSON_ASSERT (bson_append_utf8 (scope, "foo", -1, "bar", -1));
   BSON_ASSERT (
      bson_append_code_with_scope (b, "code", -1, "var a = {};", scope));
   b2 = get_bson ("test31.bson");
   BSON_ASSERT_BSON_EQUAL (b, b2);
   r = bson_iter_init_find (&iter, b, "code");
   BSON_ASSERT (r);
   BSON_ASSERT (BSON_ITER_HOLDS_CODEWSCOPE (&iter));
   code = bson_iter_codewscope (&iter, &len, &scopelen, &scope_buf);
   BSON_ASSERT (len == 11);
   BSON_ASSERT (scopelen == scope->len);
   BSON_ASSERT (!strcmp (code, "var a = {};"));
   bson_destroy (b);
   bson_destroy (b2);
   bson_destroy (scope);

   /* CDRIVER-2269 Test with a malformed zero length code string  */
   b = bson_new ();

   uint8_t data[] = {
      0x00,
      0x00,
      0x00,
      0x00, // length of doc (set below)
      0x0F, // code_w_s type
      0x00, // empty key
      0x10,
      0x00,
      0x00,
      0x00, // code_w_s length (needs to be > 14 for initial
            // validation so give a non-empty scope doc)
      0x00,
      0x00,
      0x00,
      0x00, // invalid string length (must have trailing \0)
      0x08,
      0x00,
      0x00,
      0x00, // scope doc length
      0x08,
      0x00,
      0x00, // "" : false
      0x00, // end of scope doc
      0x00  // end of doc
   };
   data[0] = (uint8_t) sizeof (data);
   b = bson_new_from_data (data, sizeof (data));

   BSON_ASSERT (b);
   BSON_ASSERT (bson_iter_init (&iter, b));
   BSON_ASSERT (!bson_iter_next (&iter));
   bson_destroy (b);

   /* CDRIVER-2269 Test with malformed BSON from ticket */
   bson_error_t err;
   bool eof;
   bson_reader_t *reader =
      bson_reader_new_from_file (BINARY_DIR "/cdriver2269.bson", &err);
   BSON_ASSERT (reader);
   const bson_t *ticketBSON = bson_reader_read (reader, &eof);
   BSON_ASSERT (ticketBSON);
   BSON_ASSERT (bson_iter_init (&iter, ticketBSON));
   BSON_ASSERT (!bson_iter_next (&iter));
   bson_reader_destroy (reader);
}

In MongoDB libbson 1.7.0, the bson_iter_codewscope function in bson-iter.c miscalculates a bson_utf8_validate length argument, which allows remote attackers to cause a denial of service (heap-based buffer over-read in the bson_utf8_validate function in bson-utf8.c), as demonstrated by bson-to-json.c.


test_bson_append_code_with_scope (void)
{
   const uint8_t *scope_buf = NULL;
   uint32_t scopelen = 0;
   uint32_t len = 0;
   bson_iter_t iter;
   bool r;
   const char *code = NULL;
   bson_t *b;
   bson_t *b2;
   bson_t *scope;
   bson_error_t err;
   bool eof;
   bson_reader_t *reader;
   const bson_t *ticket_bson;
   uint8_t malformed_data[] = {
      0x00,
      0x00,
      0x00,
      0x00, // length of doc (set below)
      0x0F, // code_w_s type
      0x00, // empty key
      0x10,
      0x00,
      0x00,
      0x00, // code_w_s length (needs to be > 14 for initial
      // validation so give a non-empty scope doc)
      0x00,
      0x00,
      0x00,
      0x00, // invalid string length (must have trailing \0)
      0x08,
      0x00,
      0x00,
      0x00, // scope doc length
      0x08,
      0x00,
      0x00, // "" : false
      0x00, // end of scope doc
      0x00  // end of doc
   };

   /* Test with NULL bson, which converts to just CODE type. */
   b = bson_new ();
   BSON_ASSERT (
      bson_append_code_with_scope (b, "code", -1, "var a = {};", NULL));
   b2 = get_bson ("test30.bson");
   BSON_ASSERT_BSON_EQUAL (b, b2);
   r = bson_iter_init_find (&iter, b, "code");
   BSON_ASSERT (r);
   BSON_ASSERT (BSON_ITER_HOLDS_CODE (&iter)); /* Not codewscope */
   bson_destroy (b);
   bson_destroy (b2);

   /* Empty scope is still CODEWSCOPE. */
   b = bson_new ();
   scope = bson_new ();
   BSON_ASSERT (
      bson_append_code_with_scope (b, "code", -1, "var a = {};", scope));
   b2 = get_bson ("code_w_empty_scope.bson");
   BSON_ASSERT_BSON_EQUAL (b, b2);
   r = bson_iter_init_find (&iter, b, "code");
   BSON_ASSERT (r);
   BSON_ASSERT (BSON_ITER_HOLDS_CODEWSCOPE (&iter));
   bson_destroy (b);
   bson_destroy (b2);
   bson_destroy (scope);

   /* Test with non-empty scope */
   b = bson_new ();
   scope = bson_new ();
   BSON_ASSERT (bson_append_utf8 (scope, "foo", -1, "bar", -1));
   BSON_ASSERT (
      bson_append_code_with_scope (b, "code", -1, "var a = {};", scope));
   b2 = get_bson ("test31.bson");
   BSON_ASSERT_BSON_EQUAL (b, b2);
   r = bson_iter_init_find (&iter, b, "code");
   BSON_ASSERT (r);
   BSON_ASSERT (BSON_ITER_HOLDS_CODEWSCOPE (&iter));
   code = bson_iter_codewscope (&iter, &len, &scopelen, &scope_buf);
   BSON_ASSERT (len == 11);
   BSON_ASSERT (scopelen == scope->len);
   BSON_ASSERT (!strcmp (code, "var a = {};"));
   bson_destroy (b);
   bson_destroy (b2);
   bson_destroy (scope);

   /* CDRIVER-2269 Test with a malformed zero length code string  */
   malformed_data[0] = (uint8_t) sizeof (malformed_data);
   b = bson_new_from_data (malformed_data, sizeof (malformed_data));
   BSON_ASSERT (b);
   BSON_ASSERT (bson_iter_init (&iter, b));
   BSON_ASSERT (!bson_iter_next (&iter));
   bson_destroy (b);

   /* CDRIVER-2269 Test with malformed BSON from ticket */
   reader = bson_reader_new_from_file (BINARY_DIR "/cdriver2269.bson", &err);
   BSON_ASSERT (reader);
   ticket_bson = bson_reader_read (reader, &eof);
   BSON_ASSERT (ticket_bson);
   BSON_ASSERT (bson_iter_init (&iter, ticket_bson));
   BSON_ASSERT (!bson_iter_next (&iter));
   bson_reader_destroy (reader);
}


lib/syscall: fix syscall registers retrieval on 32-bit platforms

Lilith >_> and Claudio Bozzato of Cisco Talos security team reported
that collect_syscall() improperly casts the syscall registers to 64-bit
values leaking the uninitialized last 24 bytes on 32-bit platforms, that
are visible in /proc/self/syscall.

The cause is that info->data.args are u64 while syscall_get_arguments()
uses longs, as hinted by the bogus pointer cast in the function.

Let's just proceed like the other call places, by retrieving the
registers into an array of longs before assigning them to the caller's
array.  This was successfully tested on x86_64, i386 and ppc32.

Reference: CVE-2020-28588, TALOS-2020-1211
Fixes: 631b7abacd02 ("ptrace: Remove maxargs from task_current_syscall()")
Cc: Greg KH <greg@kroah.com>
Reviewed-by: Kees Cook <keescook@chromium.org>
Tested-by: Michael Ellerman <mpe@ellerman.id.au> (ppc32)
Signed-off-by: Willy Tarreau <w@1wt.eu>
Reviewed-by: Thomas Gleixner <tglx@linutronix.de>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

static int collect_syscall(struct task_struct *target, struct syscall_info *info)
{
	struct pt_regs *regs;

	if (!try_get_task_stack(target)) {
		/* Task has no stack, so the task isn't in a syscall. */
		memset(info, 0, sizeof(*info));
		info->data.nr = -1;
		return 0;
	}

	regs = task_pt_regs(target);
	if (unlikely(!regs)) {
		put_task_stack(target);
		return -EAGAIN;
	}

	info->sp = user_stack_pointer(regs);
	info->data.instruction_pointer = instruction_pointer(regs);

	info->data.nr = syscall_get_nr(target, regs);
	if (info->data.nr != -1L)
		syscall_get_arguments(target, regs,
				      (unsigned long *)&info->data.args[0]);

	put_task_stack(target);
	return 0;
}

An information disclosure vulnerability exists in the /proc/pid/syscall functionality of Linux Kernel 5.1 Stable and 5.4.66. More specifically, this issue has been introduced in v5.1-rc4 (commit 631b7abacd02b88f4b0795c08b54ad4fc3e7c7c0) and is still present in v5.10-rc4, so it’s likely that all versions in between are affected. An attacker can read /proc/pid/syscall to trigger this vulnerability, which leads to the kernel leaking memory contents.


static int collect_syscall(struct task_struct *target, struct syscall_info *info)
{
	unsigned long args[6] = { };
	struct pt_regs *regs;

	if (!try_get_task_stack(target)) {
		/* Task has no stack, so the task isn't in a syscall. */
		memset(info, 0, sizeof(*info));
		info->data.nr = -1;
		return 0;
	}

	regs = task_pt_regs(target);
	if (unlikely(!regs)) {
		put_task_stack(target);
		return -EAGAIN;
	}

	info->sp = user_stack_pointer(regs);
	info->data.instruction_pointer = instruction_pointer(regs);

	info->data.nr = syscall_get_nr(target, regs);
	if (info->data.nr != -1L)
		syscall_get_arguments(target, regs, args);

	info->data.args[0] = args[0];
	info->data.args[1] = args[1];
	info->data.args[2] = args[2];
	info->data.args[3] = args[3];
	info->data.args[4] = args[4];
	info->data.args[5] = args[5];

	put_task_stack(target);
	return 0;
}


mirror: prepend ./ to rm and chmod arguments to avoid URL recognition (fix #452)

int   MirrorJob::Do()
{
   int	 res;
   int	 m=STALL;
   FileInfo *file;
   Job	 *j;

   switch(state)
   {
   case(INITIAL_STATE):
      remove_this_source_dir=(remove_source_dirs && source_dir.last_char()!='/');
      if(!strcmp(target_dir,".") || !strcmp(target_dir,"..") || (FlagSet(SCAN_ALL_FIRST) && parent_mirror))
	 create_target_dir=false;

      source_session->Chdir(source_dir);
      source_redirections=0;
      source_session->Roll();
      set_state(CHANGING_DIR_SOURCE);
      m=MOVED;
      /*fallthrough*/
   case(CHANGING_DIR_SOURCE):
      HandleChdir(source_session,source_redirections);
      if(state!=CHANGING_DIR_SOURCE)
	 return MOVED;
      if(source_session->IsOpen())
	 return m;

      source_dir.set(source_session->GetCwd().GetDirectory());

   pre_MAKE_TARGET_DIR:
   {
      if(!create_target_dir)
	 goto pre_CHANGING_DIR_TARGET;
      if(target_is_local)
      {
	 struct stat st;
	 if((FlagSet(RETR_SYMLINKS)?stat:lstat)(target_dir,&st)!=-1)
	 {
	    if(S_ISDIR(st.st_mode))
	    {
	       // try to enable write access
	       // only if not enabled as chmod can clear sgid flags on directories
	       if(!script_only && (st.st_mode!=(st.st_mode|0700)))
		  chmod(target_dir,st.st_mode|0700);
	       create_target_dir=false;
	       goto pre_CHANGING_DIR_TARGET;
	    }
	    else
	    {
	       Report(_("Removing old local file `%s'"),target_dir.get());
	       if(script)
	       {
		  ArgV args("rm");
		  args.Append(target_session->GetFileURL(target_dir));
		  xstring_ca cmd(args.CombineQuoted());
		  fprintf(script,"%s\n",cmd.get());
	       }
	       if(!script_only)
	       {
		  if(remove(target_dir)==-1)
		     eprintf("mirror: remove(%s): %s\n",target_dir.get(),strerror(errno));
	       }
	    }
	 }
      }

      if(FlagSet(DEPTH_FIRST))
	 goto pre_GETTING_LIST_INFO;

      if(target_relative_dir)
	 Report(_("Making directory `%s'"),target_relative_dir.get());
      bool mkdir_p=(parent_mirror==0 || parent_mirror->create_target_dir);
      if(script)
      {
	 ArgV args("mkdir");
	 if(mkdir_p)
	    args.Append("-p");
	 args.Append(target_session->GetFileURL(target_dir));
	 xstring_ca cmd(args.CombineQuoted());
	 fprintf(script,"%s\n",cmd.get());
	 if(script_only)
	    goto pre_CHANGING_DIR_TARGET;
      }
      target_session->Mkdir(target_dir,mkdir_p);
      set_state(MAKE_TARGET_DIR);
      m=MOVED;
   }
      /*fallthrough*/
   case(MAKE_TARGET_DIR):
      res=target_session->Done();
      if(res==FA::IN_PROGRESS)
	 return m;
      target_session->Close();
      create_target_dir=false;

   pre_CHANGING_DIR_TARGET:
      target_session->Chdir(target_dir);
      target_redirections=0;
      target_session->Roll();
      set_state(CHANGING_DIR_TARGET);
      m=MOVED;
      /*fallthrough*/
   case(CHANGING_DIR_TARGET):
      HandleChdir(target_session,target_redirections);
      if(state!=CHANGING_DIR_TARGET)
	 return MOVED;
      if(target_session->IsOpen())
	 return m;
      create_target_dir=false;

      target_dir.set(target_session->GetCwd().GetDirectory());

   pre_GETTING_LIST_INFO:
      set_state(GETTING_LIST_INFO);
      m=MOVED;
      if(!source_set)
	 HandleListInfoCreation(source_session,source_list_info,source_relative_dir);
      if(!target_set && !create_target_dir
      && (!FlagSet(DEPTH_FIRST) || FlagSet(ONLY_EXISTING))
      && !(FlagSet(TARGET_FLAT) && parent_mirror))
	 HandleListInfoCreation(target_session,target_list_info,target_relative_dir);
      if(state!=GETTING_LIST_INFO)
      {
	 source_list_info=0;
	 target_list_info=0;
      }
      return m;	  // give time to other tasks
   case(GETTING_LIST_INFO):
      HandleListInfo(source_list_info,source_set);
      HandleListInfo(target_list_info,target_set,&target_set_excluded);
      if(state!=GETTING_LIST_INFO)
	 return MOVED;
      if(source_list_info || target_list_info)
	 return m;

      MirrorFinished(); // leave room for transfers.

      if(FlagSet(DEPTH_FIRST) && source_set && !target_set)
      {
	 // transfer directories first
	 InitSets();
	 to_transfer->Unsort();
	 to_transfer->SubtractNotDirs();
	 goto pre_WAITING_FOR_TRANSFER;
      }

      // now we have both target and source file sets.
      if(parent_mirror)
	 stats.dirs++;

      if(FlagSet(SCAN_ALL_FIRST) && parent_mirror)
      {
	 source_set->PrependPath(source_relative_dir);
	 if(root_mirror->source_set_recursive)
	    root_mirror->source_set_recursive->Merge(source_set);
	 else
	    root_mirror->source_set_recursive=source_set.borrow();
	 if(target_set) {
	    target_set->PrependPath(target_relative_dir);
	    if(root_mirror->target_set_recursive)
	       root_mirror->target_set_recursive->Merge(target_set);
	    else
	       root_mirror->target_set_recursive=target_set.borrow();
	 }
	 if(target_set_excluded) {
	    target_set_excluded->PrependPath(target_relative_dir);
	    if(root_mirror->target_set_excluded)
	       root_mirror->target_set_excluded->Merge(target_set_excluded);
	    else
	       root_mirror->target_set_excluded=target_set_excluded.borrow();
	 }
	 root_mirror->stats.dirs++;
	 transfer_count++; // parent mirror will decrement it.
	 goto pre_DONE;
      }

      if(source_set_recursive) {
	 source_set->Merge(source_set_recursive);
	 source_set_recursive=0;
      }
      if(target_set_recursive) {
	 target_set->Merge(target_set_recursive);
	 target_set_recursive=0;
      }
      InitSets();

      to_transfer->CountBytes(&bytes_to_transfer);
      if(parent_mirror)
	 parent_mirror->AddBytesToTransfer(bytes_to_transfer);

      to_rm->Count(&stats.del_dirs,&stats.del_files,&stats.del_symlinks,&stats.del_files);
      to_rm->rewind();
      to_rm_mismatched->Count(&stats.del_dirs,&stats.del_files,&stats.del_symlinks,&stats.del_files);
      to_rm_mismatched->rewind();

      target_set->Merge(target_set_excluded);
      target_set_excluded=0;

      set_state(TARGET_REMOVE_OLD_FIRST);
      goto TARGET_REMOVE_OLD_FIRST_label;

   pre_TARGET_MKDIR:
      if(!to_mkdir)
	 goto pre_WAITING_FOR_TRANSFER;
      to_mkdir->rewind();
      set_state(TARGET_MKDIR);
      m=MOVED;
      /*fallthrough*/
   case(TARGET_MKDIR):
      while((j=FindDoneAwaitedJob())!=0)
      {
	 JobFinished(j);
	 m=MOVED;
      }
      if(max_error_count>0 && stats.error_count>=max_error_count)
	 goto pre_FINISHING;
      while(transfer_count<parallel && state==TARGET_MKDIR)
      {
	 file=to_mkdir->curr();
	 if(!file)
	    goto pre_WAITING_FOR_TRANSFER;
	 to_mkdir->next();
	 if(!file->TypeIs(file->DIRECTORY))
	    continue;
	 if(script)
	    fprintf(script,"mkdir %s\n",target_session->GetFileURL(file->name).get());
	 if(!script_only)
	 {
	    ArgV *a=new ArgV("mkdir");
	    a->Append(file->name);
	    mkdirJob *mkj=new mkdirJob(target_session->Clone(),a);
	    a->CombineTo(mkj->cmdline);
	    JobStarted(mkj);
	    m=MOVED;
	 }
      }
      break;

   pre_WAITING_FOR_TRANSFER:
      to_transfer->rewind();
      set_state(WAITING_FOR_TRANSFER);
      m=MOVED;
      /*fallthrough*/
   case(WAITING_FOR_TRANSFER):
      while((j=FindDoneAwaitedJob())!=0)
      {
	 TransferFinished(j);
	 m=MOVED;
      }
      if(max_error_count>0 && stats.error_count>=max_error_count)
	 goto pre_FINISHING;
      while(transfer_count<parallel && state==WAITING_FOR_TRANSFER)
      {
	 file=to_transfer->curr();
	 if(!file)
	 {
	    // go to the next step only when all transfers have finished
	    if(waiting_num>0)
	       break;
	    if(FlagSet(DEPTH_FIRST))
	    {
	       // we have been in the depth, don't go there again
	       SetFlags(DEPTH_FIRST,false);
	       SetFlags(NO_RECURSION,true);

	       // if we have not created any subdirs and there are only subdirs,
	       // then the directory would be empty - skip it.
	       if(FlagSet(NO_EMPTY_DIRS) && stats.dirs==0 && only_dirs)
		  goto pre_FINISHING_FIX_LOCAL;

	       MirrorStarted();
	       goto pre_MAKE_TARGET_DIR;
	    }
	    goto pre_TARGET_REMOVE_OLD;
	 }
	 HandleFile(file);
	 to_transfer->next();
	 m=MOVED;
      }
      break;

   pre_TARGET_REMOVE_OLD:
      if(FlagSet(REMOVE_FIRST))
	 goto pre_TARGET_CHMOD;
      set_state(TARGET_REMOVE_OLD);
      m=MOVED;
      /*fallthrough*/
   case(TARGET_REMOVE_OLD):
   case(TARGET_REMOVE_OLD_FIRST):
   TARGET_REMOVE_OLD_FIRST_label:
      while((j=FindDoneAwaitedJob())!=0)
      {
	 JobFinished(j);
	 m=MOVED;
      }
      if(max_error_count>0 && stats.error_count>=max_error_count)
	 goto pre_FINISHING;
      while(transfer_count<parallel && (state==TARGET_REMOVE_OLD || state==TARGET_REMOVE_OLD_FIRST))
      {
	 file=0;
	 if(!file && state==TARGET_REMOVE_OLD_FIRST)
	 {
	    file=to_rm_mismatched->curr();
	    to_rm_mismatched->next();
	 }
	 if(!file && (state==TARGET_REMOVE_OLD || FlagSet(REMOVE_FIRST)))
	 {
	    file=to_rm->curr();
	    to_rm->next();
	 }
	 if(!file)
	 {
	    if(waiting_num>0)
	       break;
	    if(state==TARGET_REMOVE_OLD)
	       goto pre_TARGET_CHMOD;
	    goto pre_TARGET_MKDIR;
	 }
	 if(!FlagSet(DELETE))
	 {
	    if(FlagSet(REPORT_NOT_DELETED))
	    {
	       const char *target_name_rel=dir_file(target_relative_dir,file->name);
	       if(file->TypeIs(file->DIRECTORY))
		  Report(_("Old directory `%s' is not removed"),target_name_rel);
	       else
		  Report(_("Old file `%s' is not removed"),target_name_rel);
	    }
	    continue;
	 }
	 if(script)
	 {
	    ArgV args("rm");
	    if(file->TypeIs(file->DIRECTORY))
	    {
	       if(recursion_mode==RECURSION_NEVER)
		  args.setarg(0,"rmdir");
	       else
		  args.Append("-r");
	    }
	    args.Append(target_session->GetFileURL(file->name));
	    xstring_ca cmd(args.CombineQuoted());
	    fprintf(script,"%s\n",cmd.get());
	 }
	 if(!script_only)
	 {
	    ArgV *args=new ArgV("rm");
	    args->Append(file->name);
	    args->seek(1);
	    rmJob *j=new rmJob(target_session->Clone(),args);
	    args->CombineTo(j->cmdline);
	    JobStarted(j);
	    if(file->TypeIs(file->DIRECTORY))
	    {
	       if(recursion_mode==RECURSION_NEVER)
	       {
		  args->setarg(0,"rmdir");
		  j->Rmdir();
	       }
	       else
		  j->Recurse();
	    }
	 }
	 const char *target_name_rel=dir_file(target_relative_dir,file->name);
	 if(file->TypeIs(file->DIRECTORY))
	    Report(_("Removing old directory `%s'"),target_name_rel);
	 else
	    Report(_("Removing old file `%s'"),target_name_rel);
      }
      break;

   pre_TARGET_CHMOD:
      if(FlagSet(NO_PERMS))
	 goto pre_FINISHING_FIX_LOCAL;

      to_transfer->rewind();
      if(FlagSet(TARGET_FLAT))
	 to_transfer->Sort(FileSet::BYNAME_FLAT);
      set_state(TARGET_CHMOD);
      m=MOVED;
      /*fallthrough*/
   case(TARGET_CHMOD):
      while((j=FindDoneAwaitedJob())!=0)
      {
	 JobFinished(j);
	 m=MOVED;
      }
      if(max_error_count>0 && stats.error_count>=max_error_count)
	 goto pre_FINISHING;
      while(transfer_count<parallel && state==TARGET_CHMOD)
      {
	 file=to_transfer->curr();
	 if(!file)
	    goto pre_FINISHING_FIX_LOCAL;
	 to_transfer->next();
	 if(file->TypeIs(file->SYMLINK))
	    continue;
	 if(!file->Has(file->MODE))
	    continue;
	 mode_t mode_mask=get_mode_mask();
	 mode_t def_mode=(file->TypeIs(file->DIRECTORY)?0775:0664)&~mode_mask;
	 if(target_is_local && file->mode==def_mode)
	 {
	    struct stat st;
	    if(!target_is_local || lstat(dir_file(target_dir,file->name),&st)==-1)
	       continue;
	    if((st.st_mode&07777)==(file->mode&~mode_mask))
	       continue;
	 }
	 FileInfo *target=target_set->FindByName(file->name);
	 if(target && target->filetype==file->DIRECTORY && file->filetype==file->DIRECTORY
	 && target->mode==(file->mode&~mode_mask) && (target->mode&0200))
	    continue;
	 if(script)
	 {
	    ArgV args("chmod");
	    args.Append(xstring::format("%03lo",(unsigned long)(file->mode&~mode_mask)));
	    args.Append(target_session->GetFileURL(file->name));
	    xstring_ca cmd(args.CombineQuoted());
	    fprintf(script,"%s\n",cmd.get());
	 }
	 if(!script_only)
	 {
	    ArgV *a=new ArgV("chmod");
	    a->Append(file->name);
	    a->seek(1);
	    ChmodJob *cj=new ChmodJob(target_session->Clone(),
				 file->mode&~mode_mask,a);
	    a->CombineTo(cj->cmdline);
	    if(!verbose_report)
	       cj->BeQuiet(); // chmod is not supported on all servers; be quiet.
	    JobStarted(cj);
	    m=MOVED;
	 }
      }
      break;

   pre_FINISHING_FIX_LOCAL:
      if(target_is_local && !script_only)     // FIXME
      {
	 const bool flat=FlagSet(TARGET_FLAT);
	 to_transfer->Sort(FileSet::BYNAME_FLAT);
	 to_transfer->LocalUtime(target_dir,/*only_dirs=*/true,flat);
	 if(FlagSet(ALLOW_CHOWN))
	    to_transfer->LocalChown(target_dir,flat);
	 if(!FlagSet(NO_PERMS) && same)
	    same->LocalChmod(target_dir,get_mode_mask(),flat);
	 if(FlagSet(ALLOW_CHOWN) && same)
	    same->LocalChown(target_dir,flat);
      }
      if(remove_source_files && (same || to_rm_src))
	 goto pre_SOURCE_REMOVING_SAME;
   pre_FINISHING:
      set_state(FINISHING);
      m=MOVED;
      /*fallthrough*/
   case(FINISHING):
      while((j=FindDoneAwaitedJob())!=0)
      {
	 JobFinished(j);
	 m=MOVED;
      }
      if(waiting_num>0)
	 break;

      // all jobs finished.
      if(remove_this_source_dir) {
	 // remove source directory once.
	 remove_this_source_dir=false;
	 if(script)
	 {
	    ArgV args("rmdir");
	    args.Append(source_session->GetFileURL(source_dir));
	    xstring_ca cmd(args.CombineQuoted());
	    fprintf(script,"%s\n",cmd.get());
	 }
	 if(!script_only)
	 {
	    ArgV *args=new ArgV("rmdir");
	    args->Append(source_dir);
	    args->seek(1);
	    rmJob *j=new rmJob(source_session->Clone(),args);
	    args->CombineTo(j->cmdline);
	    j->Rmdir();
	    JobStarted(j);
	 }
	 if(source_relative_dir)
	    Report(_("Removing source directory `%s'"),source_relative_dir.get());
	 m=MOVED;
	 break;
      }

      // all jobs finished and src dir removed, if needed.

      transfer_count++; // parent mirror will decrement it.
      if(parent_mirror)
	 parent_mirror->stats.Add(stats);
      else
      {
	 if(stats.HaveSomethingDone(flags) && on_change)
	 {
	    CmdExec *exec=new CmdExec(source_session->Clone(),0);
	    AddWaiting(exec);
	    exec->FeedCmd(on_change);
	    exec->FeedCmd("\n");
	    set_state(LAST_EXEC);
	    break;
	 }
      }
      goto pre_DONE;

   pre_SOURCE_REMOVING_SAME:
      if(!same)
	 same=to_rm_src.borrow();
      else if(to_rm_src)
	 same->Merge(to_rm_src);
      same->rewind();
      set_state(SOURCE_REMOVING_SAME);
      m=MOVED;
      /*fallthrough*/
   case(SOURCE_REMOVING_SAME):
      while((j=FindDoneAwaitedJob())!=0)
      {
	 JobFinished(j);
	 m=MOVED;
      }
      if(max_error_count>0 && stats.error_count>=max_error_count)
	 goto pre_FINISHING;
      while(transfer_count<parallel && state==SOURCE_REMOVING_SAME)
      {
	 file=same->curr();
	 same->next();
	 if(!file)
	    goto pre_FINISHING;
	 if(file->TypeIs(file->DIRECTORY))
	    continue;
	 if(script)
	 {
	    ArgV args("rm");
	    args.Append(source_session->GetFileURL(file->name));
	    xstring_ca cmd(args.CombineQuoted());
	    fprintf(script,"%s\n",cmd.get());
	 }
	 if(!script_only)
	 {
	    ArgV *args=new ArgV("rm");
	    args->Append(file->name);
	    args->seek(1);
	    rmJob *j=new rmJob(source_session->Clone(),args);
	    args->CombineTo(j->cmdline);
	    JobStarted(j);
	 }
	 const char *source_name_rel=dir_file(source_relative_dir,file->name);
	 Report(_("Removing source file `%s'"),source_name_rel);
      }
      break;

   case(LAST_EXEC):
      while((j=FindDoneAwaitedJob())!=0)
      {
	 RemoveWaiting(j);
	 Delete(j);
	 m=MOVED;
      }
      if(waiting_num>0)
	 break;
   pre_DONE:
      set_state(DONE);
      m=MOVED;
      bytes_transferred=0;
      if(!parent_mirror && FlagSet(LOOP) && stats.HaveSomethingDone(flags) && !stats.error_count)
      {
	 PrintStatus(0,"");
	 printf(_("Retrying mirror...\n"));
	 stats.Reset();
	 source_set=0;
	 target_set=0;
	 goto pre_GETTING_LIST_INFO;
      }
      /*fallthrough*/
   case(DONE):
      break;
   }
   // give direct parent priority over grand-parents.
   if(transfer_count<parallel && parent_mirror)
      m|=parent_mirror->Roll();
   return m;
}

It has been discovered that lftp up to and including version 4.8.3 does not properly sanitize remote file names, leading to a loss of integrity on the local system when reverse mirroring is used. A remote attacker may trick a user to use reverse mirroring on an attacker controlled FTP server, resulting in the removal of all files in the current working directory of the victim's system.


int   MirrorJob::Do()
{
   int	 res;
   int	 m=STALL;
   FileInfo *file;
   Job	 *j;

   switch(state)
   {
   case(INITIAL_STATE):
      remove_this_source_dir=(remove_source_dirs && source_dir.last_char()!='/');
      if(!strcmp(target_dir,".") || !strcmp(target_dir,"..") || (FlagSet(SCAN_ALL_FIRST) && parent_mirror))
	 create_target_dir=false;

      source_session->Chdir(source_dir);
      source_redirections=0;
      source_session->Roll();
      set_state(CHANGING_DIR_SOURCE);
      m=MOVED;
      /*fallthrough*/
   case(CHANGING_DIR_SOURCE):
      HandleChdir(source_session,source_redirections);
      if(state!=CHANGING_DIR_SOURCE)
	 return MOVED;
      if(source_session->IsOpen())
	 return m;

      source_dir.set(source_session->GetCwd().GetDirectory());

   pre_MAKE_TARGET_DIR:
   {
      if(!create_target_dir)
	 goto pre_CHANGING_DIR_TARGET;
      if(target_is_local)
      {
	 struct stat st;
	 if((FlagSet(RETR_SYMLINKS)?stat:lstat)(target_dir,&st)!=-1)
	 {
	    if(S_ISDIR(st.st_mode))
	    {
	       // try to enable write access
	       // only if not enabled as chmod can clear sgid flags on directories
	       if(!script_only && (st.st_mode!=(st.st_mode|0700)))
		  chmod(target_dir,st.st_mode|0700);
	       create_target_dir=false;
	       goto pre_CHANGING_DIR_TARGET;
	    }
	    else
	    {
	       Report(_("Removing old local file `%s'"),target_dir.get());
	       if(script)
	       {
		  ArgV args("rm");
		  args.Append(target_session->GetFileURL(target_dir));
		  xstring_ca cmd(args.CombineQuoted());
		  fprintf(script,"%s\n",cmd.get());
	       }
	       if(!script_only)
	       {
		  if(remove(target_dir)==-1)
		     eprintf("mirror: remove(%s): %s\n",target_dir.get(),strerror(errno));
	       }
	    }
	 }
      }

      if(FlagSet(DEPTH_FIRST))
	 goto pre_GETTING_LIST_INFO;

      if(target_relative_dir)
	 Report(_("Making directory `%s'"),target_relative_dir.get());
      bool mkdir_p=(parent_mirror==0 || parent_mirror->create_target_dir);
      if(script)
      {
	 ArgV args("mkdir");
	 if(mkdir_p)
	    args.Append("-p");
	 args.Append(target_session->GetFileURL(target_dir));
	 xstring_ca cmd(args.CombineQuoted());
	 fprintf(script,"%s\n",cmd.get());
	 if(script_only)
	    goto pre_CHANGING_DIR_TARGET;
      }
      target_session->Mkdir(target_dir,mkdir_p);
      set_state(MAKE_TARGET_DIR);
      m=MOVED;
   }
      /*fallthrough*/
   case(MAKE_TARGET_DIR):
      res=target_session->Done();
      if(res==FA::IN_PROGRESS)
	 return m;
      target_session->Close();
      create_target_dir=false;

   pre_CHANGING_DIR_TARGET:
      target_session->Chdir(target_dir);
      target_redirections=0;
      target_session->Roll();
      set_state(CHANGING_DIR_TARGET);
      m=MOVED;
      /*fallthrough*/
   case(CHANGING_DIR_TARGET):
      HandleChdir(target_session,target_redirections);
      if(state!=CHANGING_DIR_TARGET)
	 return MOVED;
      if(target_session->IsOpen())
	 return m;
      create_target_dir=false;

      target_dir.set(target_session->GetCwd().GetDirectory());

   pre_GETTING_LIST_INFO:
      set_state(GETTING_LIST_INFO);
      m=MOVED;
      if(!source_set)
	 HandleListInfoCreation(source_session,source_list_info,source_relative_dir);
      if(!target_set && !create_target_dir
      && (!FlagSet(DEPTH_FIRST) || FlagSet(ONLY_EXISTING))
      && !(FlagSet(TARGET_FLAT) && parent_mirror))
	 HandleListInfoCreation(target_session,target_list_info,target_relative_dir);
      if(state!=GETTING_LIST_INFO)
      {
	 source_list_info=0;
	 target_list_info=0;
      }
      return m;	  // give time to other tasks
   case(GETTING_LIST_INFO):
      HandleListInfo(source_list_info,source_set);
      HandleListInfo(target_list_info,target_set,&target_set_excluded);
      if(state!=GETTING_LIST_INFO)
	 return MOVED;
      if(source_list_info || target_list_info)
	 return m;

      MirrorFinished(); // leave room for transfers.

      if(FlagSet(DEPTH_FIRST) && source_set && !target_set)
      {
	 // transfer directories first
	 InitSets();
	 to_transfer->Unsort();
	 to_transfer->SubtractNotDirs();
	 goto pre_WAITING_FOR_TRANSFER;
      }

      // now we have both target and source file sets.
      if(parent_mirror)
	 stats.dirs++;

      if(FlagSet(SCAN_ALL_FIRST) && parent_mirror)
      {
	 source_set->PrependPath(source_relative_dir);
	 if(root_mirror->source_set_recursive)
	    root_mirror->source_set_recursive->Merge(source_set);
	 else
	    root_mirror->source_set_recursive=source_set.borrow();
	 if(target_set) {
	    target_set->PrependPath(target_relative_dir);
	    if(root_mirror->target_set_recursive)
	       root_mirror->target_set_recursive->Merge(target_set);
	    else
	       root_mirror->target_set_recursive=target_set.borrow();
	 }
	 if(target_set_excluded) {
	    target_set_excluded->PrependPath(target_relative_dir);
	    if(root_mirror->target_set_excluded)
	       root_mirror->target_set_excluded->Merge(target_set_excluded);
	    else
	       root_mirror->target_set_excluded=target_set_excluded.borrow();
	 }
	 root_mirror->stats.dirs++;
	 transfer_count++; // parent mirror will decrement it.
	 goto pre_DONE;
      }

      if(source_set_recursive) {
	 source_set->Merge(source_set_recursive);
	 source_set_recursive=0;
      }
      if(target_set_recursive) {
	 target_set->Merge(target_set_recursive);
	 target_set_recursive=0;
      }
      InitSets();

      to_transfer->CountBytes(&bytes_to_transfer);
      if(parent_mirror)
	 parent_mirror->AddBytesToTransfer(bytes_to_transfer);

      to_rm->Count(&stats.del_dirs,&stats.del_files,&stats.del_symlinks,&stats.del_files);
      to_rm->rewind();
      to_rm_mismatched->Count(&stats.del_dirs,&stats.del_files,&stats.del_symlinks,&stats.del_files);
      to_rm_mismatched->rewind();

      target_set->Merge(target_set_excluded);
      target_set_excluded=0;

      set_state(TARGET_REMOVE_OLD_FIRST);
      goto TARGET_REMOVE_OLD_FIRST_label;

   pre_TARGET_MKDIR:
      if(!to_mkdir)
	 goto pre_WAITING_FOR_TRANSFER;
      to_mkdir->rewind();
      set_state(TARGET_MKDIR);
      m=MOVED;
      /*fallthrough*/
   case(TARGET_MKDIR):
      while((j=FindDoneAwaitedJob())!=0)
      {
	 JobFinished(j);
	 m=MOVED;
      }
      if(max_error_count>0 && stats.error_count>=max_error_count)
	 goto pre_FINISHING;
      while(transfer_count<parallel && state==TARGET_MKDIR)
      {
	 file=to_mkdir->curr();
	 if(!file)
	    goto pre_WAITING_FOR_TRANSFER;
	 to_mkdir->next();
	 if(!file->TypeIs(file->DIRECTORY))
	    continue;
	 if(script)
	    fprintf(script,"mkdir %s\n",target_session->GetFileURL(file->name).get());
	 if(!script_only)
	 {
	    ArgV *a=new ArgV("mkdir");
	    a->Append(file->name);
	    mkdirJob *mkj=new mkdirJob(target_session->Clone(),a);
	    a->CombineTo(mkj->cmdline);
	    JobStarted(mkj);
	    m=MOVED;
	 }
      }
      break;

   pre_WAITING_FOR_TRANSFER:
      to_transfer->rewind();
      set_state(WAITING_FOR_TRANSFER);
      m=MOVED;
      /*fallthrough*/
   case(WAITING_FOR_TRANSFER):
      while((j=FindDoneAwaitedJob())!=0)
      {
	 TransferFinished(j);
	 m=MOVED;
      }
      if(max_error_count>0 && stats.error_count>=max_error_count)
	 goto pre_FINISHING;
      while(transfer_count<parallel && state==WAITING_FOR_TRANSFER)
      {
	 file=to_transfer->curr();
	 if(!file)
	 {
	    // go to the next step only when all transfers have finished
	    if(waiting_num>0)
	       break;
	    if(FlagSet(DEPTH_FIRST))
	    {
	       // we have been in the depth, don't go there again
	       SetFlags(DEPTH_FIRST,false);
	       SetFlags(NO_RECURSION,true);

	       // if we have not created any subdirs and there are only subdirs,
	       // then the directory would be empty - skip it.
	       if(FlagSet(NO_EMPTY_DIRS) && stats.dirs==0 && only_dirs)
		  goto pre_FINISHING_FIX_LOCAL;

	       MirrorStarted();
	       goto pre_MAKE_TARGET_DIR;
	    }
	    goto pre_TARGET_REMOVE_OLD;
	 }
	 HandleFile(file);
	 to_transfer->next();
	 m=MOVED;
      }
      break;

   pre_TARGET_REMOVE_OLD:
      if(FlagSet(REMOVE_FIRST))
	 goto pre_TARGET_CHMOD;
      set_state(TARGET_REMOVE_OLD);
      m=MOVED;
      /*fallthrough*/
   case(TARGET_REMOVE_OLD):
   case(TARGET_REMOVE_OLD_FIRST):
   TARGET_REMOVE_OLD_FIRST_label:
      while((j=FindDoneAwaitedJob())!=0)
      {
	 JobFinished(j);
	 m=MOVED;
      }
      if(max_error_count>0 && stats.error_count>=max_error_count)
	 goto pre_FINISHING;
      while(transfer_count<parallel && (state==TARGET_REMOVE_OLD || state==TARGET_REMOVE_OLD_FIRST))
      {
	 file=0;
	 if(!file && state==TARGET_REMOVE_OLD_FIRST)
	 {
	    file=to_rm_mismatched->curr();
	    to_rm_mismatched->next();
	 }
	 if(!file && (state==TARGET_REMOVE_OLD || FlagSet(REMOVE_FIRST)))
	 {
	    file=to_rm->curr();
	    to_rm->next();
	 }
	 if(!file)
	 {
	    if(waiting_num>0)
	       break;
	    if(state==TARGET_REMOVE_OLD)
	       goto pre_TARGET_CHMOD;
	    goto pre_TARGET_MKDIR;
	 }
	 if(!FlagSet(DELETE))
	 {
	    if(FlagSet(REPORT_NOT_DELETED))
	    {
	       const char *target_name_rel=dir_file(target_relative_dir,file->name);
	       if(file->TypeIs(file->DIRECTORY))
		  Report(_("Old directory `%s' is not removed"),target_name_rel);
	       else
		  Report(_("Old file `%s' is not removed"),target_name_rel);
	    }
	    continue;
	 }
	 bool use_rmdir = (file->TypeIs(file->DIRECTORY)
			   && recursion_mode==RECURSION_NEVER);
	 if(script)
	 {
	    ArgV args(use_rmdir?"rmdir":"rm");
	    if(file->TypeIs(file->DIRECTORY) && !use_rmdir)
	       args.Append("-r");
	    args.Append(target_session->GetFileURL(file->name));
	    xstring_ca cmd(args.CombineQuoted());
	    fprintf(script,"%s\n",cmd.get());
	 }
	 if(!script_only)
	 {
	    ArgV *args=new ArgV(use_rmdir?"rmdir":"rm");
	    args->Append(dir_file(".",file->name));
	    args->seek(1);
	    rmJob *j=new rmJob(target_session->Clone(),args);
	    args->CombineTo(j->cmdline);
	    JobStarted(j);
	    if(file->TypeIs(file->DIRECTORY))
	    {
	       if(recursion_mode==RECURSION_NEVER)
		  j->Rmdir();
	       else
		  j->Recurse();
	    }
	 }
	 const char *target_name_rel=dir_file(target_relative_dir,file->name);
	 if(file->TypeIs(file->DIRECTORY))
	    Report(_("Removing old directory `%s'"),target_name_rel);
	 else
	    Report(_("Removing old file `%s'"),target_name_rel);
      }
      break;

   pre_TARGET_CHMOD:
      if(FlagSet(NO_PERMS))
	 goto pre_FINISHING_FIX_LOCAL;

      to_transfer->rewind();
      if(FlagSet(TARGET_FLAT))
	 to_transfer->Sort(FileSet::BYNAME_FLAT);
      set_state(TARGET_CHMOD);
      m=MOVED;
      /*fallthrough*/
   case(TARGET_CHMOD):
      while((j=FindDoneAwaitedJob())!=0)
      {
	 JobFinished(j);
	 m=MOVED;
      }
      if(max_error_count>0 && stats.error_count>=max_error_count)
	 goto pre_FINISHING;
      while(transfer_count<parallel && state==TARGET_CHMOD)
      {
	 file=to_transfer->curr();
	 if(!file)
	    goto pre_FINISHING_FIX_LOCAL;
	 to_transfer->next();
	 if(file->TypeIs(file->SYMLINK))
	    continue;
	 if(!file->Has(file->MODE))
	    continue;
	 mode_t mode_mask=get_mode_mask();
	 mode_t def_mode=(file->TypeIs(file->DIRECTORY)?0775:0664)&~mode_mask;
	 if(target_is_local && file->mode==def_mode)
	 {
	    struct stat st;
	    if(!target_is_local || lstat(dir_file(target_dir,file->name),&st)==-1)
	       continue;
	    if((st.st_mode&07777)==(file->mode&~mode_mask))
	       continue;
	 }
	 FileInfo *target=target_set->FindByName(file->name);
	 if(target && target->filetype==file->DIRECTORY && file->filetype==file->DIRECTORY
	 && target->mode==(file->mode&~mode_mask) && (target->mode&0200))
	    continue;
	 if(script)
	 {
	    ArgV args("chmod");
	    args.Append(xstring::format("%03lo",(unsigned long)(file->mode&~mode_mask)));
	    args.Append(target_session->GetFileURL(file->name));
	    xstring_ca cmd(args.CombineQuoted());
	    fprintf(script,"%s\n",cmd.get());
	 }
	 if(!script_only)
	 {
	    ArgV *a=new ArgV("chmod");
	    a->Append(dir_file(".",file->name));
	    a->seek(1);
	    ChmodJob *cj=new ChmodJob(target_session->Clone(),
				 file->mode&~mode_mask,a);
	    a->CombineTo(cj->cmdline);
	    if(!verbose_report)
	       cj->BeQuiet(); // chmod is not supported on all servers; be quiet.
	    JobStarted(cj);
	    m=MOVED;
	 }
      }
      break;

   pre_FINISHING_FIX_LOCAL:
      if(target_is_local && !script_only)     // FIXME
      {
	 const bool flat=FlagSet(TARGET_FLAT);
	 to_transfer->Sort(FileSet::BYNAME_FLAT);
	 to_transfer->LocalUtime(target_dir,/*only_dirs=*/true,flat);
	 if(FlagSet(ALLOW_CHOWN))
	    to_transfer->LocalChown(target_dir,flat);
	 if(!FlagSet(NO_PERMS) && same)
	    same->LocalChmod(target_dir,get_mode_mask(),flat);
	 if(FlagSet(ALLOW_CHOWN) && same)
	    same->LocalChown(target_dir,flat);
      }
      if(remove_source_files && (same || to_rm_src))
	 goto pre_SOURCE_REMOVING_SAME;
   pre_FINISHING:
      set_state(FINISHING);
      m=MOVED;
      /*fallthrough*/
   case(FINISHING):
      while((j=FindDoneAwaitedJob())!=0)
      {
	 JobFinished(j);
	 m=MOVED;
      }
      if(waiting_num>0)
	 break;

      // all jobs finished.
      if(remove_this_source_dir) {
	 // remove source directory once.
	 remove_this_source_dir=false;
	 if(script)
	 {
	    ArgV args("rmdir");
	    args.Append(source_session->GetFileURL(source_dir));
	    xstring_ca cmd(args.CombineQuoted());
	    fprintf(script,"%s\n",cmd.get());
	 }
	 if(!script_only)
	 {
	    ArgV *args=new ArgV("rmdir");
	    args->Append(source_dir);
	    args->seek(1);
	    rmJob *j=new rmJob(source_session->Clone(),args);
	    args->CombineTo(j->cmdline);
	    j->Rmdir();
	    JobStarted(j);
	 }
	 if(source_relative_dir)
	    Report(_("Removing source directory `%s'"),source_relative_dir.get());
	 m=MOVED;
	 break;
      }

      // all jobs finished and src dir removed, if needed.

      transfer_count++; // parent mirror will decrement it.
      if(parent_mirror)
	 parent_mirror->stats.Add(stats);
      else
      {
	 if(stats.HaveSomethingDone(flags) && on_change)
	 {
	    CmdExec *exec=new CmdExec(source_session->Clone(),0);
	    AddWaiting(exec);
	    exec->FeedCmd(on_change);
	    exec->FeedCmd("\n");
	    set_state(LAST_EXEC);
	    break;
	 }
      }
      goto pre_DONE;

   pre_SOURCE_REMOVING_SAME:
      if(!same)
	 same=to_rm_src.borrow();
      else if(to_rm_src)
	 same->Merge(to_rm_src);
      same->rewind();
      set_state(SOURCE_REMOVING_SAME);
      m=MOVED;
      /*fallthrough*/
   case(SOURCE_REMOVING_SAME):
      while((j=FindDoneAwaitedJob())!=0)
      {
	 JobFinished(j);
	 m=MOVED;
      }
      if(max_error_count>0 && stats.error_count>=max_error_count)
	 goto pre_FINISHING;
      while(transfer_count<parallel && state==SOURCE_REMOVING_SAME)
      {
	 file=same->curr();
	 same->next();
	 if(!file)
	    goto pre_FINISHING;
	 if(file->TypeIs(file->DIRECTORY))
	    continue;
	 if(script)
	 {
	    ArgV args("rm");
	    args.Append(source_session->GetFileURL(file->name));
	    xstring_ca cmd(args.CombineQuoted());
	    fprintf(script,"%s\n",cmd.get());
	 }
	 if(!script_only)
	 {
	    ArgV *args=new ArgV("rm");
	    args->Append(dir_file(".",file->name));
	    args->seek(1);
	    rmJob *j=new rmJob(source_session->Clone(),args);
	    args->CombineTo(j->cmdline);
	    JobStarted(j);
	 }
	 const char *source_name_rel=dir_file(source_relative_dir,file->name);
	 Report(_("Removing source file `%s'"),source_name_rel);
      }
      break;

   case(LAST_EXEC):
      while((j=FindDoneAwaitedJob())!=0)
      {
	 RemoveWaiting(j);
	 Delete(j);
	 m=MOVED;
      }
      if(waiting_num>0)
	 break;
   pre_DONE:
      set_state(DONE);
      m=MOVED;
      bytes_transferred=0;
      if(!parent_mirror && FlagSet(LOOP) && stats.HaveSomethingDone(flags) && !stats.error_count)
      {
	 PrintStatus(0,"");
	 printf(_("Retrying mirror...\n"));
	 stats.Reset();
	 source_set=0;
	 target_set=0;
	 goto pre_GETTING_LIST_INFO;
      }
      /*fallthrough*/
   case(DONE):
      break;
   }
   // give direct parent priority over grand-parents.
   if(transfer_count<parallel && parent_mirror)
      m|=parent_mirror->Roll();
   return m;
}


ASoC: SOF: Fix memory leak in sof_dfsentry_write

In the implementation of sof_dfsentry_write() memory allocated for
string is leaked in case of an error. Go to error handling path if the
d_name.name is not valid.

Fixes: 091c12e1f50c ("ASoC: SOF: debug: add new debugfs entries for IPC flood test")
Signed-off-by: Navid Emamdoost <navid.emamdoost@gmail.com>
Link: https://lore.kernel.org/r/20191027194856.4056-1-navid.emamdoost@gmail.com
Signed-off-by: Mark Brown <broonie@kernel.org>

static ssize_t sof_dfsentry_write(struct file *file, const char __user *buffer,
				  size_t count, loff_t *ppos)
{
#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST)
	struct snd_sof_dfsentry *dfse = file->private_data;
	struct snd_sof_dev *sdev = dfse->sdev;
	unsigned long ipc_duration_ms = 0;
	bool flood_duration_test = false;
	unsigned long ipc_count = 0;
	struct dentry *dentry;
	int err;
#endif
	size_t size;
	char *string;
	int ret;

	string = kzalloc(count, GFP_KERNEL);
	if (!string)
		return -ENOMEM;

	size = simple_write_to_buffer(string, count, ppos, buffer, count);
	ret = size;

#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST)
	/*
	 * write op is only supported for ipc_flood_count or
	 * ipc_flood_duration_ms debugfs entries atm.
	 * ipc_flood_count floods the DSP with the number of IPC's specified.
	 * ipc_duration_ms test floods the DSP for the time specified
	 * in the debugfs entry.
	 */
	dentry = file->f_path.dentry;
	if (strcmp(dentry->d_name.name, "ipc_flood_count") &&
	    strcmp(dentry->d_name.name, "ipc_flood_duration_ms"))
		return -EINVAL;

	if (!strcmp(dentry->d_name.name, "ipc_flood_duration_ms"))
		flood_duration_test = true;

	/* test completion criterion */
	if (flood_duration_test)
		ret = kstrtoul(string, 0, &ipc_duration_ms);
	else
		ret = kstrtoul(string, 0, &ipc_count);
	if (ret < 0)
		goto out;

	/* limit max duration/ipc count for flood test */
	if (flood_duration_test) {
		if (!ipc_duration_ms) {
			ret = size;
			goto out;
		}

		/* find the minimum. min() is not used to avoid warnings */
		if (ipc_duration_ms > MAX_IPC_FLOOD_DURATION_MS)
			ipc_duration_ms = MAX_IPC_FLOOD_DURATION_MS;
	} else {
		if (!ipc_count) {
			ret = size;
			goto out;
		}

		/* find the minimum. min() is not used to avoid warnings */
		if (ipc_count > MAX_IPC_FLOOD_COUNT)
			ipc_count = MAX_IPC_FLOOD_COUNT;
	}

	ret = pm_runtime_get_sync(sdev->dev);
	if (ret < 0) {
		dev_err_ratelimited(sdev->dev,
				    "error: debugfs write failed to resume %d\n",
				    ret);
		pm_runtime_put_noidle(sdev->dev);
		goto out;
	}

	/* flood test */
	ret = sof_debug_ipc_flood_test(sdev, dfse, flood_duration_test,
				       ipc_duration_ms, ipc_count);

	pm_runtime_mark_last_busy(sdev->dev);
	err = pm_runtime_put_autosuspend(sdev->dev);
	if (err < 0)
		dev_err_ratelimited(sdev->dev,
				    "error: debugfs write failed to idle %d\n",
				    err);

	/* return size if test is successful */
	if (ret >= 0)
		ret = size;
out:
#endif
	kfree(string);
	return ret;
}

A memory leak in the sof_dfsentry_write() function in sound/soc/sof/debug.c in the Linux kernel through 5.3.9 allows attackers to cause a denial of service (memory consumption), aka CID-c0a333d842ef.


static ssize_t sof_dfsentry_write(struct file *file, const char __user *buffer,
				  size_t count, loff_t *ppos)
{
#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST)
	struct snd_sof_dfsentry *dfse = file->private_data;
	struct snd_sof_dev *sdev = dfse->sdev;
	unsigned long ipc_duration_ms = 0;
	bool flood_duration_test = false;
	unsigned long ipc_count = 0;
	struct dentry *dentry;
	int err;
#endif
	size_t size;
	char *string;
	int ret;

	string = kzalloc(count, GFP_KERNEL);
	if (!string)
		return -ENOMEM;

	size = simple_write_to_buffer(string, count, ppos, buffer, count);
	ret = size;

#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST)
	/*
	 * write op is only supported for ipc_flood_count or
	 * ipc_flood_duration_ms debugfs entries atm.
	 * ipc_flood_count floods the DSP with the number of IPC's specified.
	 * ipc_duration_ms test floods the DSP for the time specified
	 * in the debugfs entry.
	 */
	dentry = file->f_path.dentry;
	if (strcmp(dentry->d_name.name, "ipc_flood_count") &&
	    strcmp(dentry->d_name.name, "ipc_flood_duration_ms")) {
		ret = -EINVAL;
		goto out;
	}

	if (!strcmp(dentry->d_name.name, "ipc_flood_duration_ms"))
		flood_duration_test = true;

	/* test completion criterion */
	if (flood_duration_test)
		ret = kstrtoul(string, 0, &ipc_duration_ms);
	else
		ret = kstrtoul(string, 0, &ipc_count);
	if (ret < 0)
		goto out;

	/* limit max duration/ipc count for flood test */
	if (flood_duration_test) {
		if (!ipc_duration_ms) {
			ret = size;
			goto out;
		}

		/* find the minimum. min() is not used to avoid warnings */
		if (ipc_duration_ms > MAX_IPC_FLOOD_DURATION_MS)
			ipc_duration_ms = MAX_IPC_FLOOD_DURATION_MS;
	} else {
		if (!ipc_count) {
			ret = size;
			goto out;
		}

		/* find the minimum. min() is not used to avoid warnings */
		if (ipc_count > MAX_IPC_FLOOD_COUNT)
			ipc_count = MAX_IPC_FLOOD_COUNT;
	}

	ret = pm_runtime_get_sync(sdev->dev);
	if (ret < 0) {
		dev_err_ratelimited(sdev->dev,
				    "error: debugfs write failed to resume %d\n",
				    ret);
		pm_runtime_put_noidle(sdev->dev);
		goto out;
	}

	/* flood test */
	ret = sof_debug_ipc_flood_test(sdev, dfse, flood_duration_test,
				       ipc_duration_ms, ipc_count);

	pm_runtime_mark_last_busy(sdev->dev);
	err = pm_runtime_put_autosuspend(sdev->dev);
	if (err < 0)
		dev_err_ratelimited(sdev->dev,
				    "error: debugfs write failed to idle %d\n",
				    err);

	/* return size if test is successful */
	if (ret >= 0)
		ret = size;
out:
#endif
	kfree(string);
	return ret;
}


Fix CVE-2013-6420 - memory corruption in openssl_x509_parse

static time_t asn1_time_to_time_t(ASN1_UTCTIME * timestr TSRMLS_DC) /* {{{ */
{
/*
	This is how the time string is formatted:

   snprintf(p, sizeof(p), "%02d%02d%02d%02d%02d%02dZ",ts->tm_year%100,
      ts->tm_mon+1,ts->tm_mday,ts->tm_hour,ts->tm_min,ts->tm_sec);
*/

	time_t ret;
	struct tm thetime;
	char * strbuf;
	char * thestr;
	long gmadjust = 0;

	if (timestr->length < 13) {
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "extension author too lazy to parse %s correctly", timestr->data);
		return (time_t)-1;
	}

	strbuf = estrdup((char *)timestr->data);

	memset(&thetime, 0, sizeof(thetime));

	/* we work backwards so that we can use atoi more easily */

	thestr = strbuf + timestr->length - 3;

	thetime.tm_sec = atoi(thestr);
	*thestr = '\0';
	thestr -= 2;
	thetime.tm_min = atoi(thestr);
	*thestr = '\0';
	thestr -= 2;
	thetime.tm_hour = atoi(thestr);
	*thestr = '\0';
	thestr -= 2;
	thetime.tm_mday = atoi(thestr);
	*thestr = '\0';
	thestr -= 2;
	thetime.tm_mon = atoi(thestr)-1;
	*thestr = '\0';
	thestr -= 2;
	thetime.tm_year = atoi(thestr);

	if (thetime.tm_year < 68) {
		thetime.tm_year += 100;
	}

	thetime.tm_isdst = -1;
	ret = mktime(&thetime);

#if HAVE_TM_GMTOFF
	gmadjust = thetime.tm_gmtoff;
#else
	/*
	** If correcting for daylight savings time, we set the adjustment to
	** the value of timezone - 3600 seconds. Otherwise, we need to overcorrect and
	** set the adjustment to the main timezone + 3600 seconds.
	*/
	gmadjust = -(thetime.tm_isdst ? (long)timezone - 3600 : (long)timezone + 3600);
#endif
	ret += gmadjust;

	efree(strbuf);

	return ret;
}

The asn1_time_to_time_t function in ext/openssl/openssl.c in PHP before 5.3.28, 5.4.x before 5.4.23, and 5.5.x before 5.5.7 does not properly parse (1) notBefore and (2) notAfter timestamps in X.509 certificates, which allows remote attackers to execute arbitrary code or cause a denial of service (memory corruption) via a crafted certificate that is not properly handled by the openssl_x509_parse function.


static time_t asn1_time_to_time_t(ASN1_UTCTIME * timestr TSRMLS_DC) /* {{{ */
{
/*
	This is how the time string is formatted:

   snprintf(p, sizeof(p), "%02d%02d%02d%02d%02d%02dZ",ts->tm_year%100,
      ts->tm_mon+1,ts->tm_mday,ts->tm_hour,ts->tm_min,ts->tm_sec);
*/

	time_t ret;
	struct tm thetime;
	char * strbuf;
	char * thestr;
	long gmadjust = 0;

	if (ASN1_STRING_type(timestr) != V_ASN1_UTCTIME) {
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "illegal ASN1 data type for timestamp");
		return (time_t)-1;
	}

	if (ASN1_STRING_length(timestr) != strlen(ASN1_STRING_data(timestr))) {
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "illegal length in timestamp");
		return (time_t)-1;
	}

	if (ASN1_STRING_length(timestr) < 13) {
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "unable to parse time string %s correctly", timestr->data);
		return (time_t)-1;
	}

	strbuf = estrdup((char *)ASN1_STRING_data(timestr));

	memset(&thetime, 0, sizeof(thetime));

	/* we work backwards so that we can use atoi more easily */

	thestr = strbuf + ASN1_STRING_length(timestr) - 3;

	thetime.tm_sec = atoi(thestr);
	*thestr = '\0';
	thestr -= 2;
	thetime.tm_min = atoi(thestr);
	*thestr = '\0';
	thestr -= 2;
	thetime.tm_hour = atoi(thestr);
	*thestr = '\0';
	thestr -= 2;
	thetime.tm_mday = atoi(thestr);
	*thestr = '\0';
	thestr -= 2;
	thetime.tm_mon = atoi(thestr)-1;
	*thestr = '\0';
	thestr -= 2;
	thetime.tm_year = atoi(thestr);

	if (thetime.tm_year < 68) {
		thetime.tm_year += 100;
	}

	thetime.tm_isdst = -1;
	ret = mktime(&thetime);

#if HAVE_TM_GMTOFF
	gmadjust = thetime.tm_gmtoff;
#else
	/*
	** If correcting for daylight savings time, we set the adjustment to
	** the value of timezone - 3600 seconds. Otherwise, we need to overcorrect and
	** set the adjustment to the main timezone + 3600 seconds.
	*/
	gmadjust = -(thetime.tm_isdst ? (long)timezone - 3600 : (long)timezone + 3600);
#endif
	ret += gmadjust;

	efree(strbuf);

	return ret;
}


Fix memory leak while parsing some X.509 certs

int x509_get_name( unsigned char **p, const unsigned char *end,
                   x509_name *cur )
{
    int ret;
    size_t len;
    const unsigned char *end2;
    x509_name *use;

    if( ( ret = asn1_get_tag( p, end, &len,
            ASN1_CONSTRUCTED | ASN1_SET ) ) != 0 )
        return( POLARSSL_ERR_X509_INVALID_NAME + ret );

    end2 = end;
    end  = *p + len;
    use = cur;

    do
    {
        if( ( ret = x509_get_attr_type_value( p, end, use ) ) != 0 )
            return( ret );

        if( *p != end )
        {
            use->next = (x509_name *) polarssl_malloc(
                    sizeof( x509_name ) );

            if( use->next == NULL )
                return( POLARSSL_ERR_X509_MALLOC_FAILED );

            memset( use->next, 0, sizeof( x509_name ) );

            use = use->next;
        }
    }
    while( *p != end );

    /*
     * recurse until end of SEQUENCE is reached
     */
    if( *p == end2 )
        return( 0 );

    cur->next = (x509_name *) polarssl_malloc(
         sizeof( x509_name ) );

    if( cur->next == NULL )
        return( POLARSSL_ERR_X509_MALLOC_FAILED );

    memset( cur->next, 0, sizeof( x509_name ) );

    return( x509_get_name( p, end2, cur->next ) );
}

PolarSSL 1.3.8 does not properly negotiate the signature algorithm to use, which allows remote attackers to conduct downgrade attacks via unspecified vectors.


int x509_get_name( unsigned char **p, const unsigned char *end,
                   x509_name *cur )
{
    int ret;
    size_t set_len;
    const unsigned char *end_set;

    /*
     * parse first SET, restricted to 1 element
     */
    if( ( ret = asn1_get_tag( p, end, &set_len,
            ASN1_CONSTRUCTED | ASN1_SET ) ) != 0 )
        return( POLARSSL_ERR_X509_INVALID_NAME + ret );

    end_set  = *p + set_len;

    if( ( ret = x509_get_attr_type_value( p, end_set, cur ) ) != 0 )
        return( ret );

    if( *p != end_set )
        return( POLARSSL_ERR_X509_FEATURE_UNAVAILABLE );

    /*
     * recurse until end of SEQUENCE is reached
     */
    if( *p == end )
        return( 0 );

    cur->next = (x509_name *) polarssl_malloc( sizeof( x509_name ) );

    if( cur->next == NULL )
        return( POLARSSL_ERR_X509_MALLOC_FAILED );

    memset( cur->next, 0, sizeof( x509_name ) );

    return( x509_get_name( p, end, cur->next ) );
}


vt_kdsetmode: extend console locking

As per the long-suffering comment.

Reported-by: Minh Yuan <yuanmingbuaa@gmail.com>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Jiri Slaby <jirislaby@kernel.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

static int vt_k_ioctl(struct tty_struct *tty, unsigned int cmd,
		unsigned long arg, bool perm)
{
	struct vc_data *vc = tty->driver_data;
	void __user *up = (void __user *)arg;
	unsigned int console = vc->vc_num;
	int ret;

	switch (cmd) {
	case KIOCSOUND:
		if (!perm)
			return -EPERM;
		/*
		 * The use of PIT_TICK_RATE is historic, it used to be
		 * the platform-dependent CLOCK_TICK_RATE between 2.6.12
		 * and 2.6.36, which was a minor but unfortunate ABI
		 * change. kd_mksound is locked by the input layer.
		 */
		if (arg)
			arg = PIT_TICK_RATE / arg;
		kd_mksound(arg, 0);
		break;

	case KDMKTONE:
		if (!perm)
			return -EPERM;
	{
		unsigned int ticks, count;

		/*
		 * Generate the tone for the appropriate number of ticks.
		 * If the time is zero, turn off sound ourselves.
		 */
		ticks = msecs_to_jiffies((arg >> 16) & 0xffff);
		count = ticks ? (arg & 0xffff) : 0;
		if (count)
			count = PIT_TICK_RATE / count;
		kd_mksound(count, ticks);
		break;
	}

	case KDGKBTYPE:
		/*
		 * this is naïve.
		 */
		return put_user(KB_101, (char __user *)arg);

		/*
		 * These cannot be implemented on any machine that implements
		 * ioperm() in user level (such as Alpha PCs) or not at all.
		 *
		 * XXX: you should never use these, just call ioperm directly..
		 */
#ifdef CONFIG_X86
	case KDADDIO:
	case KDDELIO:
		/*
		 * KDADDIO and KDDELIO may be able to add ports beyond what
		 * we reject here, but to be safe...
		 *
		 * These are locked internally via sys_ioperm
		 */
		if (arg < GPFIRST || arg > GPLAST)
			return -EINVAL;

		return ksys_ioperm(arg, 1, (cmd == KDADDIO)) ? -ENXIO : 0;

	case KDENABIO:
	case KDDISABIO:
		return ksys_ioperm(GPFIRST, GPNUM,
				  (cmd == KDENABIO)) ? -ENXIO : 0;
#endif

	/* Linux m68k/i386 interface for setting the keyboard delay/repeat rate */

	case KDKBDREP:
	{
		struct kbd_repeat kbrep;

		if (!capable(CAP_SYS_TTY_CONFIG))
			return -EPERM;

		if (copy_from_user(&kbrep, up, sizeof(struct kbd_repeat)))
			return -EFAULT;

		ret = kbd_rate(&kbrep);
		if (ret)
			return ret;
		if (copy_to_user(up, &kbrep, sizeof(struct kbd_repeat)))
			return -EFAULT;
		break;
	}

	case KDSETMODE:
		if (!perm)
			return -EPERM;

		return vt_kdsetmode(vc, arg);

	case KDGETMODE:
		return put_user(vc->vc_mode, (int __user *)arg);

	case KDMAPDISP:
	case KDUNMAPDISP:
		/*
		 * these work like a combination of mmap and KDENABIO.
		 * this could be easily finished.
		 */
		return -EINVAL;

	case KDSKBMODE:
		if (!perm)
			return -EPERM;
		ret = vt_do_kdskbmode(console, arg);
		if (ret)
			return ret;
		tty_ldisc_flush(tty);
		break;

	case KDGKBMODE:
		return put_user(vt_do_kdgkbmode(console), (int __user *)arg);

	/* this could be folded into KDSKBMODE, but for compatibility
	   reasons it is not so easy to fold KDGKBMETA into KDGKBMODE */
	case KDSKBMETA:
		return vt_do_kdskbmeta(console, arg);

	case KDGKBMETA:
		/* FIXME: should review whether this is worth locking */
		return put_user(vt_do_kdgkbmeta(console), (int __user *)arg);

	case KDGETKEYCODE:
	case KDSETKEYCODE:
		if(!capable(CAP_SYS_TTY_CONFIG))
			perm = 0;
		return vt_do_kbkeycode_ioctl(cmd, up, perm);

	case KDGKBENT:
	case KDSKBENT:
		return vt_do_kdsk_ioctl(cmd, up, perm, console);

	case KDGKBSENT:
	case KDSKBSENT:
		return vt_do_kdgkb_ioctl(cmd, up, perm);

	/* Diacritical processing. Handled in keyboard.c as it has
	   to operate on the keyboard locks and structures */
	case KDGKBDIACR:
	case KDGKBDIACRUC:
	case KDSKBDIACR:
	case KDSKBDIACRUC:
		return vt_do_diacrit(cmd, up, perm);

	/* the ioctls below read/set the flags usually shown in the leds */
	/* don't use them - they will go away without warning */
	case KDGKBLED:
	case KDSKBLED:
	case KDGETLED:
	case KDSETLED:
		return vt_do_kdskled(console, cmd, arg, perm);

	/*
	 * A process can indicate its willingness to accept signals
	 * generated by pressing an appropriate key combination.
	 * Thus, one can have a daemon that e.g. spawns a new console
	 * upon a keypress and then changes to it.
	 * See also the kbrequest field of inittab(5).
	 */
	case KDSIGACCEPT:
		if (!perm || !capable(CAP_KILL))
			return -EPERM;
		if (!valid_signal(arg) || arg < 1 || arg == SIGKILL)
			return -EINVAL;

		spin_lock_irq(&vt_spawn_con.lock);
		put_pid(vt_spawn_con.pid);
		vt_spawn_con.pid = get_pid(task_pid(current));
		vt_spawn_con.sig = arg;
		spin_unlock_irq(&vt_spawn_con.lock);
		break;

	case KDFONTOP: {
		struct console_font_op op;

		if (copy_from_user(&op, up, sizeof(op)))
			return -EFAULT;
		if (!perm && op.op != KD_FONT_OP_GET)
			return -EPERM;
		ret = con_font_op(vc, &op);
		if (ret)
			return ret;
		if (copy_to_user(up, &op, sizeof(op)))
			return -EFAULT;
		break;
	}

	default:
		return -ENOIOCTLCMD;
	}

	return 0;
}

A race problem was seen in the vt_k_ioctl in drivers/tty/vt/vt_ioctl.c in the Linux kernel, which may cause an out of bounds read in vt as the write access to vc_mode is not protected by lock-in vt_ioctl (KDSETMDE). The highest threat from this vulnerability is to data confidentiality.


static int vt_k_ioctl(struct tty_struct *tty, unsigned int cmd,
		unsigned long arg, bool perm)
{
	struct vc_data *vc = tty->driver_data;
	void __user *up = (void __user *)arg;
	unsigned int console = vc->vc_num;
	int ret;

	switch (cmd) {
	case KIOCSOUND:
		if (!perm)
			return -EPERM;
		/*
		 * The use of PIT_TICK_RATE is historic, it used to be
		 * the platform-dependent CLOCK_TICK_RATE between 2.6.12
		 * and 2.6.36, which was a minor but unfortunate ABI
		 * change. kd_mksound is locked by the input layer.
		 */
		if (arg)
			arg = PIT_TICK_RATE / arg;
		kd_mksound(arg, 0);
		break;

	case KDMKTONE:
		if (!perm)
			return -EPERM;
	{
		unsigned int ticks, count;

		/*
		 * Generate the tone for the appropriate number of ticks.
		 * If the time is zero, turn off sound ourselves.
		 */
		ticks = msecs_to_jiffies((arg >> 16) & 0xffff);
		count = ticks ? (arg & 0xffff) : 0;
		if (count)
			count = PIT_TICK_RATE / count;
		kd_mksound(count, ticks);
		break;
	}

	case KDGKBTYPE:
		/*
		 * this is naïve.
		 */
		return put_user(KB_101, (char __user *)arg);

		/*
		 * These cannot be implemented on any machine that implements
		 * ioperm() in user level (such as Alpha PCs) or not at all.
		 *
		 * XXX: you should never use these, just call ioperm directly..
		 */
#ifdef CONFIG_X86
	case KDADDIO:
	case KDDELIO:
		/*
		 * KDADDIO and KDDELIO may be able to add ports beyond what
		 * we reject here, but to be safe...
		 *
		 * These are locked internally via sys_ioperm
		 */
		if (arg < GPFIRST || arg > GPLAST)
			return -EINVAL;

		return ksys_ioperm(arg, 1, (cmd == KDADDIO)) ? -ENXIO : 0;

	case KDENABIO:
	case KDDISABIO:
		return ksys_ioperm(GPFIRST, GPNUM,
				  (cmd == KDENABIO)) ? -ENXIO : 0;
#endif

	/* Linux m68k/i386 interface for setting the keyboard delay/repeat rate */

	case KDKBDREP:
	{
		struct kbd_repeat kbrep;

		if (!capable(CAP_SYS_TTY_CONFIG))
			return -EPERM;

		if (copy_from_user(&kbrep, up, sizeof(struct kbd_repeat)))
			return -EFAULT;

		ret = kbd_rate(&kbrep);
		if (ret)
			return ret;
		if (copy_to_user(up, &kbrep, sizeof(struct kbd_repeat)))
			return -EFAULT;
		break;
	}

	case KDSETMODE:
		if (!perm)
			return -EPERM;

		console_lock();
		ret = vt_kdsetmode(vc, arg);
		console_unlock();
		return ret;

	case KDGETMODE:
		return put_user(vc->vc_mode, (int __user *)arg);

	case KDMAPDISP:
	case KDUNMAPDISP:
		/*
		 * these work like a combination of mmap and KDENABIO.
		 * this could be easily finished.
		 */
		return -EINVAL;

	case KDSKBMODE:
		if (!perm)
			return -EPERM;
		ret = vt_do_kdskbmode(console, arg);
		if (ret)
			return ret;
		tty_ldisc_flush(tty);
		break;

	case KDGKBMODE:
		return put_user(vt_do_kdgkbmode(console), (int __user *)arg);

	/* this could be folded into KDSKBMODE, but for compatibility
	   reasons it is not so easy to fold KDGKBMETA into KDGKBMODE */
	case KDSKBMETA:
		return vt_do_kdskbmeta(console, arg);

	case KDGKBMETA:
		/* FIXME: should review whether this is worth locking */
		return put_user(vt_do_kdgkbmeta(console), (int __user *)arg);

	case KDGETKEYCODE:
	case KDSETKEYCODE:
		if(!capable(CAP_SYS_TTY_CONFIG))
			perm = 0;
		return vt_do_kbkeycode_ioctl(cmd, up, perm);

	case KDGKBENT:
	case KDSKBENT:
		return vt_do_kdsk_ioctl(cmd, up, perm, console);

	case KDGKBSENT:
	case KDSKBSENT:
		return vt_do_kdgkb_ioctl(cmd, up, perm);

	/* Diacritical processing. Handled in keyboard.c as it has
	   to operate on the keyboard locks and structures */
	case KDGKBDIACR:
	case KDGKBDIACRUC:
	case KDSKBDIACR:
	case KDSKBDIACRUC:
		return vt_do_diacrit(cmd, up, perm);

	/* the ioctls below read/set the flags usually shown in the leds */
	/* don't use them - they will go away without warning */
	case KDGKBLED:
	case KDSKBLED:
	case KDGETLED:
	case KDSETLED:
		return vt_do_kdskled(console, cmd, arg, perm);

	/*
	 * A process can indicate its willingness to accept signals
	 * generated by pressing an appropriate key combination.
	 * Thus, one can have a daemon that e.g. spawns a new console
	 * upon a keypress and then changes to it.
	 * See also the kbrequest field of inittab(5).
	 */
	case KDSIGACCEPT:
		if (!perm || !capable(CAP_KILL))
			return -EPERM;
		if (!valid_signal(arg) || arg < 1 || arg == SIGKILL)
			return -EINVAL;

		spin_lock_irq(&vt_spawn_con.lock);
		put_pid(vt_spawn_con.pid);
		vt_spawn_con.pid = get_pid(task_pid(current));
		vt_spawn_con.sig = arg;
		spin_unlock_irq(&vt_spawn_con.lock);
		break;

	case KDFONTOP: {
		struct console_font_op op;

		if (copy_from_user(&op, up, sizeof(op)))
			return -EFAULT;
		if (!perm && op.op != KD_FONT_OP_GET)
			return -EPERM;
		ret = con_font_op(vc, &op);
		if (ret)
			return ret;
		if (copy_to_user(up, &op, sizeof(op)))
			return -EFAULT;
		break;
	}

	default:
		return -ENOIOCTLCMD;
	}

	return 0;
}


Fix #491, issue with part number range check reconstructing chunk offset table

The chunk offset was incorrectly testing for a part number that was the
same size (i.e. an invalid index)

Signed-off-by: Kimball Thurston <kdt3rd@gmail.com>

MultiPartInputFile::Data::chunkOffsetReconstruction(OPENEXR_IMF_INTERNAL_NAMESPACE::IStream& is, const vector<InputPartData*>& parts)
{
    //
    // Reconstruct broken chunk offset tables. Stop once we received any exception.
    //

    Int64 position = is.tellg();

    //
    // check we understand all the parts available: if not, we cannot continue
    // exceptions thrown here should trickle back up to the constructor
    //

    for (size_t i = 0; i < parts.size(); i++)
    {
        Header& header=parts[i]->header;

        //
        // do we have a valid type entry?
        // we only need them for true multipart files or single part non-image (deep) files
        //
        if(!header.hasType() && (isMultiPart(version) || isNonImage(version)))
        {
            throw IEX_NAMESPACE::ArgExc("cannot reconstruct incomplete file: part with missing type");
        }
        if(!isSupportedType(header.type()))
        {
            throw IEX_NAMESPACE::ArgExc("cannot reconstruct incomplete file: part with unknown type "+header.type());
        }
    }

    // how many chunks should we read? We should stop when we reach the end
    size_t total_chunks = 0;

    // for tiled-based parts, array of (pointers to) tileOffsets objects
    // to create mapping between tile coordinates and chunk table indices

    vector<TileOffsets*> tileOffsets(parts.size());

    // for scanline-based parts, number of scanlines in each chunk
    vector<int> rowsizes(parts.size());

    for(size_t i = 0 ; i < parts.size() ; i++)
    {
        total_chunks += parts[i]->chunkOffsets.size();
        if (isTiled(parts[i]->header.type()))
        {
            tileOffsets[i] = createTileOffsets(parts[i]->header);
        }else{
            tileOffsets[i] = NULL;
            // (TODO) fix this so that it doesn't need to be revised for future compression types.
            switch(parts[i]->header.compression())
            {
                case DWAB_COMPRESSION :
                    rowsizes[i] = 256;
                    break;
                case PIZ_COMPRESSION :
                case B44_COMPRESSION :
                case B44A_COMPRESSION :
                case DWAA_COMPRESSION :
                    rowsizes[i]=32;
                    break;
                case ZIP_COMPRESSION :
                case PXR24_COMPRESSION :
                    rowsizes[i]=16;
                    break;
                case ZIPS_COMPRESSION :
                case RLE_COMPRESSION :
                case NO_COMPRESSION :
                    rowsizes[i]=1;
                    break;
                default :
                    throw(IEX_NAMESPACE::ArgExc("Unknown compression method in chunk offset reconstruction"));
            }
        }
     }

     try
     {

        //
        //
        //

        Int64 chunk_start = position;
        for (size_t i = 0; i < total_chunks ; i++)
        {
            //
            // do we have a part number?
            //

            int partNumber = 0;
            if(isMultiPart(version))
            {
                OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (is, partNumber);
            }

            if(partNumber<0 || partNumber> static_cast<int>(parts.size()))
            {
                throw IEX_NAMESPACE::IoExc("part number out of range");
            }

            Header& header = parts[partNumber]->header;

            // size of chunk NOT including multipart field

            Int64 size_of_chunk=0;

            if (isTiled(header.type()))
            {
                //
                //
                //
                int tilex,tiley,levelx,levely;
                OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (is, tilex);
                OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (is, tiley);
                OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (is, levelx);
                OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (is, levely);

                //std::cout << "chunk_start for " << tilex <<',' << tiley << ',' << levelx << ' ' << levely << ':' << chunk_start << std::endl;

                if(!tileOffsets[partNumber])
                {
                    // this shouldn't actually happen - we should have allocated a valid
                    // tileOffsets for any part which isTiled
                    throw IEX_NAMESPACE::IoExc("part not tiled");

                }

                if(!tileOffsets[partNumber]->isValidTile(tilex,tiley,levelx,levely))
                {
                    throw IEX_NAMESPACE::IoExc("invalid tile coordinates");
                }

                (*tileOffsets[partNumber])(tilex,tiley,levelx,levely)=chunk_start;

                // compute chunk sizes - different procedure for deep tiles and regular
                // ones
                if(header.type()==DEEPTILE)
                {
                    Int64 packed_offset;
                    Int64 packed_sample;
                    OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (is, packed_offset);
                    OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (is, packed_sample);

                    //add 40 byte header to packed sizes (tile coordinates, packed sizes, unpacked size)
                    size_of_chunk=packed_offset+packed_sample+40;
                }
                else
                {

                    // regular image has 20 bytes of header, 4 byte chunksize;
                    int chunksize;
                    OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (is, chunksize);
                    size_of_chunk=chunksize+20;
                }
            }
            else
            {
                int y_coordinate;
                OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (is, y_coordinate);

                if(y_coordinate < header.dataWindow().min.y || y_coordinate > header.dataWindow().max.y)
                {
                   throw IEX_NAMESPACE::IoExc("y out of range");
                }
                y_coordinate -= header.dataWindow().min.y;
                y_coordinate /= rowsizes[partNumber];

                if(y_coordinate < 0 || y_coordinate >= int(parts[partNumber]->chunkOffsets.size()))
                {
                   throw IEX_NAMESPACE::IoExc("chunk index out of range");
                }

                parts[partNumber]->chunkOffsets[y_coordinate]=chunk_start;

                if(header.type()==DEEPSCANLINE)
                {
                    Int64 packed_offset;
                    Int64 packed_sample;
                    OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (is, packed_offset);
                    OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (is, packed_sample);

                    size_of_chunk=packed_offset+packed_sample+28;
                }
                else
                {
                    int chunksize;
                    OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (is, chunksize);
                    size_of_chunk=chunksize+8;
                }

            }

            if(isMultiPart(version))
            {
                chunk_start+=4;
            }

            chunk_start+=size_of_chunk;

            is.seekg(chunk_start);

        }

    }
    catch (...)
    {
        //
        // Suppress all exceptions.  This functions is
        // called only to reconstruct the line offset
        // table for incomplete files, and exceptions
        // are likely.
        //
    }

    // copy tiled part data back to chunk offsets

    for(size_t partNumber=0;partNumber<parts.size();partNumber++)
    {
        if(tileOffsets[partNumber])
        {
            size_t pos=0;
            vector<vector<vector <Int64> > > offsets = tileOffsets[partNumber]->getOffsets();
            for (size_t l = 0; l < offsets.size(); l++)
                for (size_t y = 0; y < offsets[l].size(); y++)
                    for (size_t x = 0; x < offsets[l][y].size(); x++)
                    {
                        parts[ partNumber ]->chunkOffsets[pos] = offsets[l][y][x];
                        pos++;
                    }
           delete tileOffsets[partNumber];
        }
    }

    is.clear();
    is.seekg (position);
}

A heap-based buffer overflow vulnerability exists in Academy Software Foundation OpenEXR 2.3.0 in chunkOffsetReconstruction in ImfMultiPartInputFile.cpp that can cause a denial of service via a crafted EXR file.


MultiPartInputFile::Data::chunkOffsetReconstruction(OPENEXR_IMF_INTERNAL_NAMESPACE::IStream& is, const vector<InputPartData*>& parts)
{
    //
    // Reconstruct broken chunk offset tables. Stop once we received any exception.
    //

    Int64 position = is.tellg();

    //
    // check we understand all the parts available: if not, we cannot continue
    // exceptions thrown here should trickle back up to the constructor
    //

    for (size_t i = 0; i < parts.size(); i++)
    {
        Header& header=parts[i]->header;

        //
        // do we have a valid type entry?
        // we only need them for true multipart files or single part non-image (deep) files
        //
        if(!header.hasType() && (isMultiPart(version) || isNonImage(version)))
        {
            throw IEX_NAMESPACE::ArgExc("cannot reconstruct incomplete file: part with missing type");
        }
        if(!isSupportedType(header.type()))
        {
            throw IEX_NAMESPACE::ArgExc("cannot reconstruct incomplete file: part with unknown type "+header.type());
        }
    }

    // how many chunks should we read? We should stop when we reach the end
    size_t total_chunks = 0;

    // for tiled-based parts, array of (pointers to) tileOffsets objects
    // to create mapping between tile coordinates and chunk table indices

    vector<TileOffsets*> tileOffsets(parts.size());

    // for scanline-based parts, number of scanlines in each chunk
    vector<int> rowsizes(parts.size());

    for(size_t i = 0 ; i < parts.size() ; i++)
    {
        total_chunks += parts[i]->chunkOffsets.size();
        if (isTiled(parts[i]->header.type()))
        {
            tileOffsets[i] = createTileOffsets(parts[i]->header);
        }else{
            tileOffsets[i] = NULL;
            // (TODO) fix this so that it doesn't need to be revised for future compression types.
            switch(parts[i]->header.compression())
            {
                case DWAB_COMPRESSION :
                    rowsizes[i] = 256;
                    break;
                case PIZ_COMPRESSION :
                case B44_COMPRESSION :
                case B44A_COMPRESSION :
                case DWAA_COMPRESSION :
                    rowsizes[i]=32;
                    break;
                case ZIP_COMPRESSION :
                case PXR24_COMPRESSION :
                    rowsizes[i]=16;
                    break;
                case ZIPS_COMPRESSION :
                case RLE_COMPRESSION :
                case NO_COMPRESSION :
                    rowsizes[i]=1;
                    break;
                default :
                    throw(IEX_NAMESPACE::ArgExc("Unknown compression method in chunk offset reconstruction"));
            }
        }
     }

     try
     {

        //
        //
        //

        Int64 chunk_start = position;
        for (size_t i = 0; i < total_chunks ; i++)
        {
            //
            // do we have a part number?
            //

            int partNumber = 0;
            if(isMultiPart(version))
            {
                OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (is, partNumber);
            }

            if(partNumber<0 || partNumber>= static_cast<int>(parts.size()))
            {
                throw IEX_NAMESPACE::IoExc("part number out of range");
            }

            Header& header = parts[partNumber]->header;

            // size of chunk NOT including multipart field

            Int64 size_of_chunk=0;

            if (isTiled(header.type()))
            {
                //
                //
                //
                int tilex,tiley,levelx,levely;
                OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (is, tilex);
                OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (is, tiley);
                OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (is, levelx);
                OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (is, levely);

                //std::cout << "chunk_start for " << tilex <<',' << tiley << ',' << levelx << ' ' << levely << ':' << chunk_start << std::endl;

                if(!tileOffsets[partNumber])
                {
                    // this shouldn't actually happen - we should have allocated a valid
                    // tileOffsets for any part which isTiled
                    throw IEX_NAMESPACE::IoExc("part not tiled");

                }

                if(!tileOffsets[partNumber]->isValidTile(tilex,tiley,levelx,levely))
                {
                    throw IEX_NAMESPACE::IoExc("invalid tile coordinates");
                }

                (*tileOffsets[partNumber])(tilex,tiley,levelx,levely)=chunk_start;

                // compute chunk sizes - different procedure for deep tiles and regular
                // ones
                if(header.type()==DEEPTILE)
                {
                    Int64 packed_offset;
                    Int64 packed_sample;
                    OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (is, packed_offset);
                    OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (is, packed_sample);

                    //add 40 byte header to packed sizes (tile coordinates, packed sizes, unpacked size)
                    size_of_chunk=packed_offset+packed_sample+40;
                }
                else
                {

                    // regular image has 20 bytes of header, 4 byte chunksize;
                    int chunksize;
                    OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (is, chunksize);
                    size_of_chunk=chunksize+20;
                }
            }
            else
            {
                int y_coordinate;
                OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (is, y_coordinate);

                if(y_coordinate < header.dataWindow().min.y || y_coordinate > header.dataWindow().max.y)
                {
                   throw IEX_NAMESPACE::IoExc("y out of range");
                }
                y_coordinate -= header.dataWindow().min.y;
                y_coordinate /= rowsizes[partNumber];

                if(y_coordinate < 0 || y_coordinate >= int(parts[partNumber]->chunkOffsets.size()))
                {
                   throw IEX_NAMESPACE::IoExc("chunk index out of range");
                }

                parts[partNumber]->chunkOffsets[y_coordinate]=chunk_start;

                if(header.type()==DEEPSCANLINE)
                {
                    Int64 packed_offset;
                    Int64 packed_sample;
                    OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (is, packed_offset);
                    OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (is, packed_sample);

                    size_of_chunk=packed_offset+packed_sample+28;
                }
                else
                {
                    int chunksize;
                    OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (is, chunksize);
                    size_of_chunk=chunksize+8;
                }

            }

            if(isMultiPart(version))
            {
                chunk_start+=4;
            }

            chunk_start+=size_of_chunk;

            is.seekg(chunk_start);

        }

    }
    catch (...)
    {
        //
        // Suppress all exceptions.  This functions is
        // called only to reconstruct the line offset
        // table for incomplete files, and exceptions
        // are likely.
        //
    }

    // copy tiled part data back to chunk offsets

    for(size_t partNumber=0;partNumber<parts.size();partNumber++)
    {
        if(tileOffsets[partNumber])
        {
            size_t pos=0;
            vector<vector<vector <Int64> > > offsets = tileOffsets[partNumber]->getOffsets();
            for (size_t l = 0; l < offsets.size(); l++)
                for (size_t y = 0; y < offsets[l].size(); y++)
                    for (size_t x = 0; x < offsets[l][y].size(); x++)
                    {
                        parts[ partNumber ]->chunkOffsets[pos] = offsets[l][y][x];
                        pos++;
                    }
           delete tileOffsets[partNumber];
        }
    }

    is.clear();
    is.seekg (position);
}


ALSA: rawmidi: Change resized buffers atomically

The SNDRV_RAWMIDI_IOCTL_PARAMS ioctl may resize the buffers and the
current code is racy.  For example, the sequencer client may write to
buffer while it being resized.

As a simple workaround, let's switch to the resized buffer inside the
stream runtime lock.

Reported-by: syzbot+52f83f0ea8df16932f7f@syzkaller.appspotmail.com
Cc: <stable@vger.kernel.org>
Signed-off-by: Takashi Iwai <tiwai@suse.de>

int snd_rawmidi_input_params(struct snd_rawmidi_substream *substream,
			     struct snd_rawmidi_params * params)
{
	char *newbuf;
	struct snd_rawmidi_runtime *runtime = substream->runtime;

	snd_rawmidi_drain_input(substream);
	if (params->buffer_size < 32 || params->buffer_size > 1024L * 1024L) {
		return -EINVAL;
	}
	if (params->avail_min < 1 || params->avail_min > params->buffer_size) {
		return -EINVAL;
	}
	if (params->buffer_size != runtime->buffer_size) {
		newbuf = krealloc(runtime->buffer, params->buffer_size,
				  GFP_KERNEL);
		if (!newbuf)
			return -ENOMEM;
		runtime->buffer = newbuf;
		runtime->buffer_size = params->buffer_size;
	}
	runtime->avail_min = params->avail_min;
	return 0;
}

It was found that the raw midi kernel driver does not protect against concurrent access which leads to a double realloc (double free) in snd_rawmidi_input_params() and snd_rawmidi_output_status() which are part of snd_rawmidi_ioctl() handler in rawmidi.c file. A malicious local attacker could possibly use this for privilege escalation.


int snd_rawmidi_input_params(struct snd_rawmidi_substream *substream,
			     struct snd_rawmidi_params * params)
{
	char *newbuf, *oldbuf;
	struct snd_rawmidi_runtime *runtime = substream->runtime;

	snd_rawmidi_drain_input(substream);
	if (params->buffer_size < 32 || params->buffer_size > 1024L * 1024L) {
		return -EINVAL;
	}
	if (params->avail_min < 1 || params->avail_min > params->buffer_size) {
		return -EINVAL;
	}
	if (params->buffer_size != runtime->buffer_size) {
		newbuf = kmalloc(params->buffer_size, GFP_KERNEL);
		if (!newbuf)
			return -ENOMEM;
		spin_lock_irq(&runtime->lock);
		oldbuf = runtime->buffer;
		runtime->buffer = newbuf;
		runtime->buffer_size = params->buffer_size;
		runtime->appl_ptr = runtime->hw_ptr = 0;
		spin_unlock_irq(&runtime->lock);
		kfree(oldbuf);
	}
	runtime->avail_min = params->avail_min;
	return 0;
}


curl: check data size before memcpy to local buffer. (CVE-2014-0144)

curl_read_cb is callback function for libcurl when data arrives. The
data size passed in here is not guaranteed to be within the range of
request we submitted, so we may overflow the guest IO buffer. Check the
real size we have before memcpy to buffer to avoid overflow.

Signed-off-by: Fam Zheng <famz@redhat.com>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
Reviewed-by: Max Reitz <mreitz@redhat.com>
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>

static size_t curl_read_cb(void *ptr, size_t size, size_t nmemb, void *opaque)
{
    CURLState *s = ((CURLState*)opaque);
    size_t realsize = size * nmemb;
    int i;

    DPRINTF("CURL: Just reading %zd bytes\n", realsize);

    if (!s || !s->orig_buf)
        goto read_end;

    memcpy(s->orig_buf + s->buf_off, ptr, realsize);
    s->buf_off += realsize;

    for(i=0; i<CURL_NUM_ACB; i++) {
        CURLAIOCB *acb = s->acb[i];

        if (!acb)
            continue;

        if ((s->buf_off >= acb->end)) {
            qemu_iovec_from_buf(acb->qiov, 0, s->orig_buf + acb->start,
                                acb->end - acb->start);
            acb->common.cb(acb->common.opaque, 0);
            qemu_aio_release(acb);
            s->acb[i] = NULL;
        }
    }

read_end:
    return realsize;
}

QEMU before 2.0.0 block drivers for CLOOP, QCOW2 version 2 and various other image formats are vulnerable to potential memory corruptions, integer/buffer overflows or crash caused by missing input validations which could allow a remote user to execute arbitrary code on the host with the privileges of the QEMU process.


static size_t curl_read_cb(void *ptr, size_t size, size_t nmemb, void *opaque)
{
    CURLState *s = ((CURLState*)opaque);
    size_t realsize = size * nmemb;
    int i;

    DPRINTF("CURL: Just reading %zd bytes\n", realsize);

    if (!s || !s->orig_buf)
        goto read_end;

    if (s->buf_off >= s->buf_len) {
        /* buffer full, read nothing */
        return 0;
    }
    realsize = MIN(realsize, s->buf_len - s->buf_off);
    memcpy(s->orig_buf + s->buf_off, ptr, realsize);
    s->buf_off += realsize;

    for(i=0; i<CURL_NUM_ACB; i++) {
        CURLAIOCB *acb = s->acb[i];

        if (!acb)
            continue;

        if ((s->buf_off >= acb->end)) {
            qemu_iovec_from_buf(acb->qiov, 0, s->orig_buf + acb->start,
                                acb->end - acb->start);
            acb->common.cb(acb->common.opaque, 0);
            qemu_aio_release(acb);
            s->acb[i] = NULL;
        }
    }

read_end:
    return realsize;
}


lib: check frame_size is >= INT32_MAX

When parsing a frame header, validate that the frame_size is less than
or equal to INT32_MAX. Given frame_max is limited between 0 and
INT32_MAX in amqp_login and friends, this does not change the API.

This prevents a potential buffer overflow when a malicious client sends
a frame_size that is close to UINT32_MAX, in which causes an overflow
when computing state->target_size resulting in a small value there. A
buffer is then allocated with the small amount, then memcopy copies the
frame_size writing to memory beyond the end of the buffer.

int amqp_handle_input(amqp_connection_state_t state, amqp_bytes_t received_data,
                      amqp_frame_t *decoded_frame) {
  size_t bytes_consumed;
  void *raw_frame;

  /* Returning frame_type of zero indicates either insufficient input,
     or a complete, ignored frame was read. */
  decoded_frame->frame_type = 0;

  if (received_data.len == 0) {
    return AMQP_STATUS_OK;
  }

  if (state->state == CONNECTION_STATE_IDLE) {
    state->state = CONNECTION_STATE_HEADER;
  }

  bytes_consumed = consume_data(state, &received_data);

  /* do we have target_size data yet? if not, return with the
     expectation that more will arrive */
  if (state->inbound_offset < state->target_size) {
    return (int)bytes_consumed;
  }

  raw_frame = state->inbound_buffer.bytes;

  switch (state->state) {
    case CONNECTION_STATE_INITIAL:
      /* check for a protocol header from the server */
      if (memcmp(raw_frame, "AMQP", 4) == 0) {
        decoded_frame->frame_type = AMQP_PSEUDOFRAME_PROTOCOL_HEADER;
        decoded_frame->channel = 0;

        decoded_frame->payload.protocol_header.transport_high =
            amqp_d8(amqp_offset(raw_frame, 4));
        decoded_frame->payload.protocol_header.transport_low =
            amqp_d8(amqp_offset(raw_frame, 5));
        decoded_frame->payload.protocol_header.protocol_version_major =
            amqp_d8(amqp_offset(raw_frame, 6));
        decoded_frame->payload.protocol_header.protocol_version_minor =
            amqp_d8(amqp_offset(raw_frame, 7));

        return_to_idle(state);
        return (int)bytes_consumed;
      }

    /* it's not a protocol header; fall through to process it as a
       regular frame header */

    case CONNECTION_STATE_HEADER: {
      amqp_channel_t channel;
      amqp_pool_t *channel_pool;
      /* frame length is 3 bytes in */
      channel = amqp_d16(amqp_offset(raw_frame, 1));

      state->target_size =
          amqp_d32(amqp_offset(raw_frame, 3)) + HEADER_SIZE + FOOTER_SIZE;

      if ((size_t)state->frame_max < state->target_size) {
        return AMQP_STATUS_BAD_AMQP_DATA;
      }

      channel_pool = amqp_get_or_create_channel_pool(state, channel);
      if (NULL == channel_pool) {
        return AMQP_STATUS_NO_MEMORY;
      }

      amqp_pool_alloc_bytes(channel_pool, state->target_size,
                            &state->inbound_buffer);
      if (NULL == state->inbound_buffer.bytes) {
        return AMQP_STATUS_NO_MEMORY;
      }
      memcpy(state->inbound_buffer.bytes, state->header_buffer, HEADER_SIZE);
      raw_frame = state->inbound_buffer.bytes;

      state->state = CONNECTION_STATE_BODY;

      bytes_consumed += consume_data(state, &received_data);

      /* do we have target_size data yet? if not, return with the
         expectation that more will arrive */
      if (state->inbound_offset < state->target_size) {
        return (int)bytes_consumed;
      }
    }
    /* fall through to process body */

    case CONNECTION_STATE_BODY: {
      amqp_bytes_t encoded;
      int res;
      amqp_pool_t *channel_pool;

      /* Check frame end marker (footer) */
      if (amqp_d8(amqp_offset(raw_frame, state->target_size - 1)) !=
          AMQP_FRAME_END) {
        return AMQP_STATUS_BAD_AMQP_DATA;
      }

      decoded_frame->frame_type = amqp_d8(amqp_offset(raw_frame, 0));
      decoded_frame->channel = amqp_d16(amqp_offset(raw_frame, 1));

      channel_pool =
          amqp_get_or_create_channel_pool(state, decoded_frame->channel);
      if (NULL == channel_pool) {
        return AMQP_STATUS_NO_MEMORY;
      }

      switch (decoded_frame->frame_type) {
        case AMQP_FRAME_METHOD:
          decoded_frame->payload.method.id =
              amqp_d32(amqp_offset(raw_frame, HEADER_SIZE));
          encoded.bytes = amqp_offset(raw_frame, HEADER_SIZE + 4);
          encoded.len = state->target_size - HEADER_SIZE - 4 - FOOTER_SIZE;

          res = amqp_decode_method(decoded_frame->payload.method.id,
                                   channel_pool, encoded,
                                   &decoded_frame->payload.method.decoded);
          if (res < 0) {
            return res;
          }

          break;

        case AMQP_FRAME_HEADER:
          decoded_frame->payload.properties.class_id =
              amqp_d16(amqp_offset(raw_frame, HEADER_SIZE));
          /* unused 2-byte weight field goes here */
          decoded_frame->payload.properties.body_size =
              amqp_d64(amqp_offset(raw_frame, HEADER_SIZE + 4));
          encoded.bytes = amqp_offset(raw_frame, HEADER_SIZE + 12);
          encoded.len = state->target_size - HEADER_SIZE - 12 - FOOTER_SIZE;
          decoded_frame->payload.properties.raw = encoded;

          res = amqp_decode_properties(
              decoded_frame->payload.properties.class_id, channel_pool, encoded,
              &decoded_frame->payload.properties.decoded);
          if (res < 0) {
            return res;
          }

          break;

        case AMQP_FRAME_BODY:
          decoded_frame->payload.body_fragment.len =
              state->target_size - HEADER_SIZE - FOOTER_SIZE;
          decoded_frame->payload.body_fragment.bytes =
              amqp_offset(raw_frame, HEADER_SIZE);
          break;

        case AMQP_FRAME_HEARTBEAT:
          break;

        default:
          /* Ignore the frame */
          decoded_frame->frame_type = 0;
          break;
      }

      return_to_idle(state);
      return (int)bytes_consumed;
    }

    default:
      amqp_abort("Internal error: invalid amqp_connection_state_t->state %d",
                 state->state);
  }
}

An issue was discovered in amqp_handle_input in amqp_connection.c in rabbitmq-c 0.9.0. There is an integer overflow that leads to heap memory corruption in the handling of CONNECTION_STATE_HEADER. A rogue server could return a malicious frame header that leads to a smaller target_size value than needed. This condition is then carried on to a memcpy function that copies too much data into a heap buffer.


int amqp_handle_input(amqp_connection_state_t state, amqp_bytes_t received_data,
                      amqp_frame_t *decoded_frame) {
  size_t bytes_consumed;
  void *raw_frame;

  /* Returning frame_type of zero indicates either insufficient input,
     or a complete, ignored frame was read. */
  decoded_frame->frame_type = 0;

  if (received_data.len == 0) {
    return AMQP_STATUS_OK;
  }

  if (state->state == CONNECTION_STATE_IDLE) {
    state->state = CONNECTION_STATE_HEADER;
  }

  bytes_consumed = consume_data(state, &received_data);

  /* do we have target_size data yet? if not, return with the
     expectation that more will arrive */
  if (state->inbound_offset < state->target_size) {
    return (int)bytes_consumed;
  }

  raw_frame = state->inbound_buffer.bytes;

  switch (state->state) {
    case CONNECTION_STATE_INITIAL:
      /* check for a protocol header from the server */
      if (memcmp(raw_frame, "AMQP", 4) == 0) {
        decoded_frame->frame_type = AMQP_PSEUDOFRAME_PROTOCOL_HEADER;
        decoded_frame->channel = 0;

        decoded_frame->payload.protocol_header.transport_high =
            amqp_d8(amqp_offset(raw_frame, 4));
        decoded_frame->payload.protocol_header.transport_low =
            amqp_d8(amqp_offset(raw_frame, 5));
        decoded_frame->payload.protocol_header.protocol_version_major =
            amqp_d8(amqp_offset(raw_frame, 6));
        decoded_frame->payload.protocol_header.protocol_version_minor =
            amqp_d8(amqp_offset(raw_frame, 7));

        return_to_idle(state);
        return (int)bytes_consumed;
      }

    /* it's not a protocol header; fall through to process it as a
       regular frame header */

    case CONNECTION_STATE_HEADER: {
      amqp_channel_t channel;
      amqp_pool_t *channel_pool;
      uint32_t frame_size;

      channel = amqp_d16(amqp_offset(raw_frame, 1));

      /* frame length is 3 bytes in */
      frame_size = amqp_d32(amqp_offset(raw_frame, 3));
      /* To prevent the target_size calculation below from overflowing, check
       * that the stated frame_size is smaller than a signed 32-bit. Given
       * the library only allows configuring frame_max as an int32_t, and
       * frame_size is uint32_t, the math below is safe from overflow. */
      if (frame_size >= INT32_MAX) {
        return AMQP_STATUS_BAD_AMQP_DATA;
      }

      state->target_size = frame_size + HEADER_SIZE + FOOTER_SIZE;
      if ((size_t)state->frame_max < state->target_size) {
        return AMQP_STATUS_BAD_AMQP_DATA;
      }

      channel_pool = amqp_get_or_create_channel_pool(state, channel);
      if (NULL == channel_pool) {
        return AMQP_STATUS_NO_MEMORY;
      }

      amqp_pool_alloc_bytes(channel_pool, state->target_size,
                            &state->inbound_buffer);
      if (NULL == state->inbound_buffer.bytes) {
        return AMQP_STATUS_NO_MEMORY;
      }
      memcpy(state->inbound_buffer.bytes, state->header_buffer, HEADER_SIZE);
      raw_frame = state->inbound_buffer.bytes;

      state->state = CONNECTION_STATE_BODY;

      bytes_consumed += consume_data(state, &received_data);

      /* do we have target_size data yet? if not, return with the
         expectation that more will arrive */
      if (state->inbound_offset < state->target_size) {
        return (int)bytes_consumed;
      }
    }
    /* fall through to process body */

    case CONNECTION_STATE_BODY: {
      amqp_bytes_t encoded;
      int res;
      amqp_pool_t *channel_pool;

      /* Check frame end marker (footer) */
      if (amqp_d8(amqp_offset(raw_frame, state->target_size - 1)) !=
          AMQP_FRAME_END) {
        return AMQP_STATUS_BAD_AMQP_DATA;
      }

      decoded_frame->frame_type = amqp_d8(amqp_offset(raw_frame, 0));
      decoded_frame->channel = amqp_d16(amqp_offset(raw_frame, 1));

      channel_pool =
          amqp_get_or_create_channel_pool(state, decoded_frame->channel);
      if (NULL == channel_pool) {
        return AMQP_STATUS_NO_MEMORY;
      }

      switch (decoded_frame->frame_type) {
        case AMQP_FRAME_METHOD:
          decoded_frame->payload.method.id =
              amqp_d32(amqp_offset(raw_frame, HEADER_SIZE));
          encoded.bytes = amqp_offset(raw_frame, HEADER_SIZE + 4);
          encoded.len = state->target_size - HEADER_SIZE - 4 - FOOTER_SIZE;

          res = amqp_decode_method(decoded_frame->payload.method.id,
                                   channel_pool, encoded,
                                   &decoded_frame->payload.method.decoded);
          if (res < 0) {
            return res;
          }

          break;

        case AMQP_FRAME_HEADER:
          decoded_frame->payload.properties.class_id =
              amqp_d16(amqp_offset(raw_frame, HEADER_SIZE));
          /* unused 2-byte weight field goes here */
          decoded_frame->payload.properties.body_size =
              amqp_d64(amqp_offset(raw_frame, HEADER_SIZE + 4));
          encoded.bytes = amqp_offset(raw_frame, HEADER_SIZE + 12);
          encoded.len = state->target_size - HEADER_SIZE - 12 - FOOTER_SIZE;
          decoded_frame->payload.properties.raw = encoded;

          res = amqp_decode_properties(
              decoded_frame->payload.properties.class_id, channel_pool, encoded,
              &decoded_frame->payload.properties.decoded);
          if (res < 0) {
            return res;
          }

          break;

        case AMQP_FRAME_BODY:
          decoded_frame->payload.body_fragment.len =
              state->target_size - HEADER_SIZE - FOOTER_SIZE;
          decoded_frame->payload.body_fragment.bytes =
              amqp_offset(raw_frame, HEADER_SIZE);
          break;

        case AMQP_FRAME_HEARTBEAT:
          break;

        default:
          /* Ignore the frame */
          decoded_frame->frame_type = 0;
          break;
      }

      return_to_idle(state);
      return (int)bytes_consumed;
    }

    default:
      amqp_abort("Internal error: invalid amqp_connection_state_t->state %d",
                 state->state);
  }
}