Commit message: MDEV-26351 segfault - (MARIA_HA *) 0x0 in ha_maria::extra

use the correct check. before invoking handler methods we
need to know that the table was opened, not only created.
CVE: CVE-2021-46663
CWE: NVD-CWE-noinfo
Vulnerability description: MariaDB through 10.5.13 allows a ha_maria::extra application crash via certain SELECT statements.

Project: server

static int rr_from_tempfile(READ_RECORD *info)
{
  int tmp;
  for (;;)
  {
    if (my_b_read(info->io_cache,info->ref_pos,info->ref_length))
      return -1;					/* End of file */
    if (!(tmp= info->table->file->ha_rnd_pos(info->record,info->ref_pos)))
      break;
    /* The following is extremely unlikely to happen */
    if (tmp == HA_ERR_RECORD_DELETED ||
        (tmp == HA_ERR_KEY_NOT_FOUND && info->ignore_not_found_rows))
      continue;
    tmp= rr_handle_error(info, tmp);
    break;
  }
  return tmp;
} /* rr_from_tempfile */

Project: server

static int rr_unpack_from_buffer(READ_RECORD *info)
{
  if (info->cache_pos == info->cache_end)
    return -1;                      /* End of buffer */
  (*info->unpack)(info->addon_field, info->cache_pos,
                  info->cache_end);
  info->cache_pos+= info->ref_length;
  return 0;
}

Project: server

static int rr_quick(READ_RECORD *info)
{
  int tmp;
  while ((tmp= info->select->quick->get_next()))
  {
    if (info->thd->killed || (tmp != HA_ERR_RECORD_DELETED))
    {
      tmp= rr_handle_error(info, tmp);
      break;
    }
  }
  return tmp;
}

Project: server

void free_cache(READ_RECORD *info)
{
  if (info->cache)
  {
    my_free_lock(info->cache);
    info->cache=0;
  }
}

Project: server

static int rr_unpack_from_tempfile(READ_RECORD *info)
{
  if (my_b_read(info->io_cache, info->rec_buf, info->ref_length))
    return -1;
  (*info->unpack)(info->addon_field, info->rec_buf,
                  info->rec_buf + info->ref_length);

  return 0;
}

Project: server

int rr_from_pointers(READ_RECORD *info)
{
  int tmp;
  uchar *cache_pos;

  for (;;)
  {
    if (info->cache_pos == info->cache_end)
      return -1;					/* End of file */
    cache_pos= info->cache_pos;
    info->cache_pos+= info->ref_length;

    if (!(tmp= info->table->file->ha_rnd_pos(info->record,cache_pos)))
      break;

    /* The following is extremely unlikely to happen */
    if (tmp == HA_ERR_RECORD_DELETED ||
        (tmp == HA_ERR_KEY_NOT_FOUND && info->ignore_not_found_rows))
      continue;
    tmp= rr_handle_error(info, tmp);
    break;
  }
  return tmp;
}

Project: server

bool init_read_record_idx(READ_RECORD *info, THD *thd, TABLE *table,
                          bool print_error, uint idx, bool reverse)
{
  int error= 0;
  DBUG_ENTER("init_read_record_idx");

  empty_record(table);
  bzero((char*) info,sizeof(*info));
  info->thd= thd;
  info->table= table;
  info->record= table->record[0];
  info->print_error= print_error;
  info->unlock_row= rr_unlock_row;

  table->status=0;			/* And it's always found */
  if (!table->file->inited &&
      (error= table->file->ha_index_init(idx, 1)))
  {
    if (print_error)
      table->file->print_error(error, MYF(0));
  }

  /* read_record will be changed to rr_index in rr_index_first */
  info->read_record= reverse ? rr_index_last : rr_index_first;
  DBUG_RETURN(error != 0);
}

Project: server

static int rr_index_last(READ_RECORD *info)
{
  int tmp= info->table->file->ha_index_last(info->record);
  info->read_record= rr_index_desc;
  if (tmp)
    tmp= rr_handle_error(info, tmp);
  return tmp;
}

Project: server

static int rr_handle_error(READ_RECORD *info, int error)
{
  if (info->thd->killed)
  {
    info->thd->send_kill_message();
    return 1;
  }

  if (error == HA_ERR_END_OF_FILE)
    error= -1;
  else
  {
    if (info->print_error)
      info->table->file->print_error(error, MYF(0));
    if (error < 0)                            // Fix negative BDB errno
      error= 1;
  }
  return error;
}

Project: server

static int rr_index_desc(READ_RECORD *info)
{
  int tmp= info->table->file->ha_index_prev(info->record);
  if (tmp)
    tmp= rr_handle_error(info, tmp);
  return tmp;
}

Project: server

static int init_rr_cache(THD *thd, READ_RECORD *info)
{
  uint rec_cache_size;
  DBUG_ENTER("init_rr_cache");

  info->struct_length= 3+MAX_REFLENGTH;
  info->reclength= ALIGN_SIZE(info->table->s->reclength+1);
  if (info->reclength < info->struct_length)
    info->reclength= ALIGN_SIZE(info->struct_length);

  info->error_offset= info->table->s->reclength;
  info->cache_records= (thd->variables.read_rnd_buff_size /
                        (info->reclength+info->struct_length));
  rec_cache_size= info->cache_records*info->reclength;
  info->rec_cache_size= info->cache_records*info->ref_length;

  // We have to allocate one more byte to use uint3korr (see comments for it)
  if (info->cache_records <= 2 ||
      !(info->cache=(uchar*) my_malloc_lock(rec_cache_size+info->cache_records*
					   info->struct_length+1,
					   MYF(MY_THREAD_SPECIFIC))))
    DBUG_RETURN(1);
#ifdef HAVE_valgrind
  // Avoid warnings in qsort
  bzero(info->cache,rec_cache_size+info->cache_records* info->struct_length+1);
#endif
  DBUG_PRINT("info",("Allocated buffert for %d records",info->cache_records));
  info->read_positions=info->cache+rec_cache_size;
  info->cache_pos=info->cache_end=info->cache;
  DBUG_RETURN(0);
} /* init_rr_cache */

Project: server

static int rr_from_cache(READ_RECORD *info)
{
  uint i;
  ulong length;
  my_off_t rest_of_file;
  int16 error;
  uchar *position,*ref_position,*record_pos;
  ulong record;

  for (;;)
  {
    if (info->cache_pos != info->cache_end)
    {
      if (info->cache_pos[info->error_offset])
      {
	shortget(error,info->cache_pos);
	if (info->print_error)
	  info->table->file->print_error(error,MYF(0));
      }
      else
      {
	error=0;
	memcpy(info->record,info->cache_pos,
               (size_t) info->table->s->reclength);
      }
      info->cache_pos+=info->reclength;
      return ((int) error);
    }
    length=info->rec_cache_size;
    rest_of_file=info->io_cache->end_of_file - my_b_tell(info->io_cache);
    if ((my_off_t) length > rest_of_file)
      length= (ulong) rest_of_file;
    if (!length || my_b_read(info->io_cache,info->cache,length))
    {
      DBUG_PRINT("info",("Found end of file"));
      return -1;			/* End of file */
    }

    length/=info->ref_length;
    position=info->cache;
    ref_position=info->read_positions;
    for (i=0 ; i < length ; i++,position+=info->ref_length)
    {
      memcpy(ref_position,position,(size_t) info->ref_length);
      ref_position+=MAX_REFLENGTH;
      int3store(ref_position,(long) i);
      ref_position+=3;
    }
    my_qsort(info->read_positions, length, info->struct_length,
             (qsort_cmp) rr_cmp);

    position=info->read_positions;
    for (i=0 ; i < length ; i++)
    {
      memcpy(info->ref_pos,position,(size_t) info->ref_length);
      position+=MAX_REFLENGTH;
      record=uint3korr(position);
      position+=3;
      record_pos=info->cache+record*info->reclength;
      if ((error=(int16) info->table->file->ha_rnd_pos(record_pos,info->ref_pos)))
      {
	record_pos[info->error_offset]=1;
	shortstore(record_pos,error);
	DBUG_PRINT("error",("Got error: %d:%d when reading row",
			    my_errno, error));
      }
      else
	record_pos[info->error_offset]=0;
    }
    info->cache_end=(info->cache_pos=info->cache)+length*info->reclength;
  }
} /* rr_from_cache */

Project: server

static int rr_cmp(uchar *a,uchar *b)
{
  if (a[0] != b[0])
    return (int) a[0] - (int) b[0];
  if (a[1] != b[1])
    return (int) a[1] - (int) b[1];
  if (a[2] != b[2])
    return (int) a[2] - (int) b[2];
#if MAX_REFLENGTH == 4
  return (int) a[3] - (int) b[3];
#else
  if (a[3] != b[3])
    return (int) a[3] - (int) b[3];
  if (a[4] != b[4])
    return (int) a[4] - (int) b[4];
  if (a[5] != b[5])
    return (int) a[5] - (int) b[5];
  if (a[6] != b[6])
    return (int) a[6] - (int) b[6];
  return (int) a[7] - (int) b[7];
#endif
}

Project: server

static int rr_index_first(READ_RECORD *info)
{
  int tmp;
  // tell handler that we are doing an index scan
  if ((tmp = info->table->file->prepare_index_scan())) 
  {
    tmp= rr_handle_error(info, tmp);
    return tmp;
  }

  tmp= info->table->file->ha_index_first(info->record);
  info->read_record= rr_index;
  if (tmp)
    tmp= rr_handle_error(info, tmp);
  return tmp;
}

Project: server

void end_read_record(READ_RECORD *info)
{
  /* free cache if used */
  free_cache(info);
  if (info->table)
  {
    if (info->table->db_stat) // if opened
      (void) info->table->file->extra(HA_EXTRA_NO_CACHE);
    if (info->read_record != rr_quick) // otherwise quick_range does it
      (void) info->table->file->ha_index_or_rnd_end();
    info->table=0;
  }
}

Project: server

bool init_read_record(READ_RECORD *info,THD *thd, TABLE *table,
		      SQL_SELECT *select,
                      SORT_INFO *filesort,
		      int use_record_cache, bool print_error, 
                      bool disable_rr_cache)
{
  IO_CACHE *tempfile;
  SORT_ADDON_FIELD *addon_field= filesort ? filesort->addon_field : 0;
  DBUG_ENTER("init_read_record");

  bzero((char*) info,sizeof(*info));
  info->thd=thd;
  info->table=table;
  info->forms= &info->table;		/* Only one table */
  info->addon_field= addon_field;
  
  if ((table->s->tmp_table == INTERNAL_TMP_TABLE) &&
      !addon_field)
    (void) table->file->extra(HA_EXTRA_MMAP);
  
  if (addon_field)
  {
    info->rec_buf=    (uchar*) filesort->addon_buf.str;
    info->ref_length= filesort->addon_buf.length;
    info->unpack=     filesort->unpack;
  }
  else
  {
    empty_record(table);
    info->record= table->record[0];
    info->ref_length= table->file->ref_length;
  }
  info->select=select;
  info->print_error=print_error;
  info->unlock_row= rr_unlock_row;
  info->ignore_not_found_rows= 0;
  table->status= 0;			/* Rows are always found */

  tempfile= 0;
  if (select && my_b_inited(&select->file))
    tempfile= &select->file;
  else if (filesort && my_b_inited(&filesort->io_cache))
    tempfile= &filesort->io_cache;

  if (tempfile && !(select && select->quick))
  {
    DBUG_PRINT("info",("using rr_from_tempfile"));
    info->read_record= (addon_field ?
                        rr_unpack_from_tempfile : rr_from_tempfile);
    info->io_cache= tempfile;
    reinit_io_cache(info->io_cache,READ_CACHE,0L,0,0);
    info->ref_pos=table->file->ref;
    if (!table->file->inited)
      if (table->file->ha_rnd_init_with_error(0))
        DBUG_RETURN(1);

    /*
      addon_field is checked because if we use addon fields,
      it doesn't make sense to use cache - we don't read from the table
      and filesort->io_cache is read sequentially
    */
    if (!disable_rr_cache &&
        !addon_field &&
	thd->variables.read_rnd_buff_size &&
	!(table->file->ha_table_flags() & HA_FAST_KEY_READ) &&
	(table->db_stat & HA_READ_ONLY ||
	 table->reginfo.lock_type <= TL_READ_NO_INSERT) &&
	(ulonglong) table->s->reclength* (table->file->stats.records+
                                          table->file->stats.deleted) >
	(ulonglong) MIN_FILE_LENGTH_TO_USE_ROW_CACHE &&
	info->io_cache->end_of_file/info->ref_length * table->s->reclength >
	(my_off_t) MIN_ROWS_TO_USE_TABLE_CACHE &&
	!table->s->blob_fields &&
        info->ref_length <= MAX_REFLENGTH)
    {
      if (! init_rr_cache(thd, info))
      {
	DBUG_PRINT("info",("using rr_from_cache"));
	info->read_record=rr_from_cache;
      }
    }
  }
  else if (select && select->quick)
  {
    DBUG_PRINT("info",("using rr_quick"));
    info->read_record=rr_quick;
  }
  else if (filesort && filesort->record_pointers)
  {
    DBUG_PRINT("info",("using record_pointers"));
    if (table->file->ha_rnd_init_with_error(0))
      DBUG_RETURN(1);
    info->cache_pos= filesort->record_pointers;
    info->cache_end= (info->cache_pos+ 
                      filesort->return_rows * info->ref_length);
    info->read_record= (addon_field ?
                        rr_unpack_from_buffer : rr_from_pointers);
  }
  else if (table->file->keyread_enabled())
  {
    int error;
    info->read_record= rr_index_first;
    if (!table->file->inited &&
        (error= table->file->ha_index_init(table->file->keyread, 1)))
    {
      if (print_error)
        table->file->print_error(error, MYF(0));
      DBUG_RETURN(1);
    }
  }
  else
  {
    DBUG_PRINT("info",("using rr_sequential"));
    info->read_record=rr_sequential;
    if (table->file->ha_rnd_init_with_error(1))
      DBUG_RETURN(1);
    /* We can use record cache if we don't update dynamic length tables */
    if (!table->no_cache &&
	(use_record_cache > 0 ||
	 (int) table->reginfo.lock_type <= (int) TL_READ_HIGH_PRIORITY ||
	 !(table->s->db_options_in_use & HA_OPTION_PACK_RECORD) ||
	 (use_record_cache < 0 &&
	  !(table->file->ha_table_flags() & HA_NOT_DELETE_WITH_CACHE))))
      (void) table->file->extra_opt(HA_EXTRA_CACHE,
                                    thd->variables.read_buff_size);
  }
  /* Condition pushdown to storage engine */
  if ((table->file->ha_table_flags() & HA_CAN_TABLE_CONDITION_PUSHDOWN) &&
      select && select->cond && 
      (select->cond->used_tables() & table->map) &&
      !table->file->pushed_cond)
    table->file->cond_push(select->cond);

  DBUG_RETURN(0);
} /* init_read_record */

Project: server

static int rr_index(READ_RECORD *info)
{
  int tmp= info->table->file->ha_index_next(info->record);
  if (tmp)
    tmp= rr_handle_error(info, tmp);
  return tmp;
}

Project: server

int rr_sequential(READ_RECORD *info)
{
  int tmp;
  while ((tmp= info->table->file->ha_rnd_next(info->record)))
  {
    /*
      rnd_next can return RECORD_DELETED for MyISAM when one thread is
      reading and another deleting without locks.
    */
    if (info->thd->killed || (tmp != HA_ERR_RECORD_DELETED))
    {
      tmp= rr_handle_error(info, tmp);
      break;
    }
  }
  return tmp;
}