Bug #80462 Accessing uninitialized memory inside sql/field.cc set_null() line ~1644
Submitted: 22 Feb 2016 3:38 Modified: 5 Sep 2019 12:25
Reporter: Sergey Sprogis Email Updates:
Status: Won't fix Impact on me:
None 
Category:MySQL Server: Compiling Severity:S3 (Non-critical)
Version:5.7.10 OS:Solaris (Sparc 11.3)
Assigned to: CPU Architecture:Any

[22 Feb 2016 3:38] Sergey Sprogis
Description:
Oracle's tool called Discover detects UMR (Uninitialized Memory Read)
in mysqld server when executing '1st' test case located inside
MySQL 5.7.10. This UMR was produced on Sparc Solaris 11.3 T7 system,
but it's not platform specific, and can occur on any other platform.

Error message and call stack reported by Discover looks like this:
======================================
(UMR): accessing uninitialized data ... (1 byte) on the heap:
 Field::set_null(long long) + 0x258 <field.cc : 1644>
  1644:=> m_null_ptr[row_offset]|= null_bit;
 set_field_to_null_with_conversions(Field*, bool) + 0x128 <field_conv.cc : 190>
  190:=>  field->set_null();
 Item_null::save_in_field_inner(Field*, bool) + 0x15c <item.cc : 6699>
  6699:=> return set_field_to_null_with_conversions(field, no_conversions);
 Item::save_in_field(Field*, bool) + 0x2bc <item.cc : 6723>
  6723:=> const type_conversion_status ret= save_in_field_inner(field, no_conversions);
 sp_eval_expr(THD*, Field*, Item**) + 0x7e8 <sp.cc : 2729>
  2729:=> expr_item->save_in_field(result_field, false);
 sp_rcontext::set_variable(THD*, Field*, Item**) + 0x214 <sp_rcontext.cc : 458>
  458:=>  return sp_eval_expr(thd, field, value);
 sp_rcontext::set_variable(THD*, unsigned int, Item**) + 0x3bc <sp_rcontext.h : 156>
  156:=>  { return set_variable(thd, m_var_table->field[var_idx], value); }
 sp_head::execute_procedure(THD*, List<Item>*) + 0x255c <sp_head.cc : 1433>
  1433:=>  proc_runtime_ctx->set_variable(thd, i, (Item **)&null_item))

Was allocated at (8192 bytes) [t@25]:
 my_raw_malloc() + 0x2a4 <my_malloc.c : 191>
  191:=>    point= malloc(size);
 my_malloc() + 0x1a4 <my_malloc.c : 54>
  54:=>  mh= (my_memory_header*) my_raw_malloc(raw_size, flags);
 alloc_root() + 0xea0 <my_alloc.c : 280>
  280:=>  get_size,MYF(MY_WME | ME_FATALERROR))))
 multi_alloc_root() + 0x650 <my_alloc.c : 344>
  344:=>  if (!(start= (char*) alloc_root(root, tot_length)))
 create_virtual_tmp_table(THD*, List<Create_field>&) + 0x570 <sql_tmp_table.cc : 1958>
  1958:=>                         NullS))
 sp_rcontext::init_var_table(THD*) + 0x558 <sp_rcontext.cc : 119>
  119:=>  if (!(m_var_table= create_virtual_tmp_table(thd, field_def_lst)))
 sp_rcontext::create(THD*, const sp_pcontext*, Field*) + 0x42c <sp_rcontext.cc : 76>
  76:=>   ctx->init_var_items(thd))
 sp_head::execute_procedure(THD*, List<Item>*) + 0xb48 <sp_head.cc : 1368>
  1368:=> parent_sp_runtime_ctx= sp_rcontext::create(thd, m_root_parsing_ctx, NULL);
======================================

How to repeat:
1. If you have access to Discover tool, and latest Sun Studio C/C++ compilers
   you need to build MySQL-5.7.10 sources in debug mode on Solaris Sparc,
   then launch discover binary with input argument as mysqld binary,
   and when completed to take a look inside mysql.html file which should
   contain call stack shown in description above.

2. If you do not have access to Discover, you need to debug MySQL server
   with '1st' test case, and to see that m_null_ptr[row_offset] operand located
   inside inside sql/field.cc set_null() line ~1644

    m_null_ptr[row_offset]|= null_bit;

   is indeed uninitialized before that point.

   Below is 24 lines independent executable t.cc test case which accurately
   simulates original issue. For it Discover produces exactly the same UMR
   message as for original mysqld server.

t.cc
===================================
#include <stdlib.h>
class Field {
 public:
  unsigned char null_bit;
  void set_null_ptr(unsigned char *p_null_ptr, unsigned p_null_bit) {
   m_null_ptr=p_null_ptr;
   null_bit=p_null_bit;
  }
  void set_null(long long row_offset=0);
 private: unsigned char *m_null_ptr;
};
void Field::set_null(long long row_offset) {
  m_null_ptr[row_offset] |= null_bit;
}
int main() {
 unsigned char *ptr;
 unsigned bit=1;
 Field F;
 ptr=(unsigned char*)malloc(8);
 F.set_null_ptr(ptr,bit);
 F.set_null();
 return 0;
}
===================================

Suggested fix:
Somehow initialize 'm_null_ptr[row_offset]' operand before read its valuefrom:
 m_null_ptr[row_offset]|= null_bit;
[22 Feb 2016 12:57] MySQL Verification Team
Hello Sergey,

Thank you for the report!

Thanks,
Umesh
[5 Sep 2019 12:25] Erlend Dahl
[30 Jul 2019 5:09] Dyre Tjeldvoll

Not an UMR according to the normal definition as control flow does not depend
on uninitialized value. We merely turn on a bit in an otherwise uninitialized
byte.
Repro not flagged by Memory Sanitizer.