Commit d5e92895 authored by Davi Arnaut's avatar Davi Arnaut

Bug#51817: incorrect assumption: thd->query at 0x2ab2a8360360 is an invalid pointer

The problem is that the logic which checks if a pointer is
valid relies on a poor heuristic based on the start and end
addresses of the data segment and heap.

Apart from miscalculating the heap bounds, this approach also
suffers from the fact that memory can come from places other
than the heap. See Bug#58528 for a more detailed explanation.

On Linux, the solution is to access the process's memory
through /proc/self/task/<tid>/mem, which allows for retrieving
the contents of pages within the virtual address space of
the calling process. If a address range is not mapped, a
input/output error is returned.

client/mysqltest.cc:
  Use new interface to my_safe_print_str.
include/my_stacktrace.h:
  Drop name from my_safe_print_str.
mysys/stacktrace.c:
  Access the process's memory through a file descriptor and
  dump the contents of the memory range. The file descriptor
  offset is equivalent to a offset into the address space.
  
  Do not print the name of the variable associated with the
  address. It can be better accomplished at a higher level.
sql/mysqld.cc:
  Put the variable dumping information within its own newline block.
  Use symbolic names which better convey information to the user.
parent 2bdeabe7
...@@ -7782,13 +7782,16 @@ static void dump_backtrace(void) ...@@ -7782,13 +7782,16 @@ static void dump_backtrace(void)
{ {
struct st_connection *conn= cur_con; struct st_connection *conn= cur_con;
my_safe_print_str("read_command_buf", read_command_buf, fprintf(stderr, "read_command_buf (%p): ", read_command_buf);
sizeof(read_command_buf)); my_safe_print_str(read_command_buf, sizeof(read_command_buf));
if (conn) if (conn)
{ {
my_safe_print_str("conn->name", conn->name, conn->name_len); fprintf(stderr, "conn->name (%p): ", conn->name);
my_safe_print_str(conn->name, conn->name_len);
#ifdef EMBEDDED_LIBRARY #ifdef EMBEDDED_LIBRARY
my_safe_print_str("conn->cur_query", conn->cur_query, conn->cur_query_len); fprintf(stderr, "conn->cur_query (%p): ", conn->cur_query);
my_safe_print_str(conn->cur_query, conn->cur_query_len);
#endif #endif
} }
fputs("Attempting backtrace...\n", stderr); fputs("Attempting backtrace...\n", stderr);
......
...@@ -47,7 +47,7 @@ C_MODE_START ...@@ -47,7 +47,7 @@ C_MODE_START
#if defined(HAVE_STACKTRACE) || defined(HAVE_BACKTRACE) #if defined(HAVE_STACKTRACE) || defined(HAVE_BACKTRACE)
void my_init_stacktrace(); void my_init_stacktrace();
void my_print_stacktrace(uchar* stack_bottom, ulong thread_stack); void my_print_stacktrace(uchar* stack_bottom, ulong thread_stack);
void my_safe_print_str(const char* name, const char* val, int max_len); void my_safe_print_str(const char* val, int max_len);
void my_write_core(int sig); void my_write_core(int sig);
#if BACKTRACE_DEMANGLE #if BACKTRACE_DEMANGLE
char *my_demangle(const char *mangled_name, int *status); char *my_demangle(const char *mangled_name, int *status);
......
...@@ -27,6 +27,11 @@ ...@@ -27,6 +27,11 @@
#include <unistd.h> #include <unistd.h>
#include <strings.h> #include <strings.h>
#ifdef __linux__
#include <ctype.h> /* isprint */
#include <sys/syscall.h> /* SYS_gettid */
#endif
#if HAVE_EXECINFO_H #if HAVE_EXECINFO_H
#include <execinfo.h> #include <execinfo.h>
#endif #endif
...@@ -46,10 +51,96 @@ void my_init_stacktrace() ...@@ -46,10 +51,96 @@ void my_init_stacktrace()
#endif #endif
} }
void my_safe_print_str(const char* name, const char* val, int max_len) #ifdef __linux__
static void print_buffer(char *buffer, size_t count)
{
for (; count && *buffer; --count)
{
int c= (int) *buffer++;
fputc(isprint(c) ? c : ' ', stderr);
}
}
/**
Access the pages of this process through /proc/self/task/<tid>/mem
in order to safely print the contents of a memory address range.
@param addr The address at the start of the memory region.
@param max_len The length of the memory region.
@return Zero on success.
*/
static int safe_print_str(const char *addr, int max_len)
{
int fd;
pid_t tid;
off_t offset;
ssize_t nbytes= 0;
size_t total, count;
char buf[256];
tid= (pid_t) syscall(SYS_gettid);
sprintf(buf, "/proc/self/task/%d/mem", tid);
if ((fd= open(buf, O_RDONLY)) < 0)
return -1;
total= max_len;
offset= (off_t) addr;
/* Read up to the maximum number of bytes. */
while (total)
{
count= min(sizeof(buf), total);
if ((nbytes= pread(fd, buf, count, offset)) < 0)
{
/* Just in case... */
if (errno == EINTR)
continue;
else
break;
}
/* Advance offset into memory. */
total-= nbytes;
offset+= nbytes;
addr+= nbytes;
/* Output the printable characters. */
print_buffer(buf, nbytes);
/* Break if less than requested... */
if ((count - nbytes))
break;
}
/* Output a new line if something was printed. */
if (total != (size_t) max_len)
fputc('\n', stderr);
if (nbytes == -1)
fprintf(stderr, "Can't read from address %p: %m.\n", addr);
close(fd);
return 0;
}
#endif
void my_safe_print_str(const char* val, int max_len)
{ {
char *heap_end= (char*) sbrk(0); char *heap_end;
fprintf(stderr, "%s at %p ", name, val);
#ifdef __linux__
if (!safe_print_str(val, max_len))
return;
#endif
heap_end= (char*) sbrk(0);
if (!PTR_SANE(val)) if (!PTR_SANE(val))
{ {
...@@ -57,7 +148,6 @@ void my_safe_print_str(const char* name, const char* val, int max_len) ...@@ -57,7 +148,6 @@ void my_safe_print_str(const char* name, const char* val, int max_len)
return; return;
} }
fprintf(stderr, "= ");
for (; max_len && PTR_SANE(val) && *val; --max_len) for (; max_len && PTR_SANE(val) && *val; --max_len)
fputc(*val++, stderr); fputc(*val++, stderr);
fputc('\n', stderr); fputc('\n', stderr);
...@@ -657,9 +747,8 @@ void my_write_core(int unused) ...@@ -657,9 +747,8 @@ void my_write_core(int unused)
} }
void my_safe_print_str(const char *name, const char *val, int len) void my_safe_print_str(const char *val, int len)
{ {
fprintf(stderr,"%s at %p", name, val);
__try __try
{ {
fprintf(stderr,"=%.*s\n", len, val); fprintf(stderr,"=%.*s\n", len, val);
......
...@@ -2527,7 +2527,7 @@ the thread stack. Please read http://dev.mysql.com/doc/mysql/en/linux.html\n\n", ...@@ -2527,7 +2527,7 @@ the thread stack. Please read http://dev.mysql.com/doc/mysql/en/linux.html\n\n",
if (!(test_flags & TEST_NO_STACKTRACE)) if (!(test_flags & TEST_NO_STACKTRACE))
{ {
fprintf(stderr, "thd: 0x%lx\n",(long) thd); fprintf(stderr, "Thread pointer: 0x%lx\n", (long) thd);
fprintf(stderr, "Attempting backtrace. You can use the following " fprintf(stderr, "Attempting backtrace. You can use the following "
"information to find out\nwhere mysqld died. If " "information to find out\nwhere mysqld died. If "
"you see no messages after this, something went\n" "you see no messages after this, something went\n"
...@@ -2555,11 +2555,13 @@ the thread stack. Please read http://dev.mysql.com/doc/mysql/en/linux.html\n\n", ...@@ -2555,11 +2555,13 @@ the thread stack. Please read http://dev.mysql.com/doc/mysql/en/linux.html\n\n",
kreason= "KILLED_NO_VALUE"; kreason= "KILLED_NO_VALUE";
break; break;
} }
fprintf(stderr, "Trying to get some variables.\n\ fprintf(stderr, "\nTrying to get some variables.\n"
Some pointers may be invalid and cause the dump to abort...\n"); "Some pointers may be invalid and cause the dump to abort.\n");
my_safe_print_str("thd->query", thd->query(), 1024); fprintf(stderr, "Query (%p): ", thd->query());
fprintf(stderr, "thd->thread_id=%lu\n", (ulong) thd->thread_id); my_safe_print_str(thd->query(), min(1024, thd->query_length()));
fprintf(stderr, "thd->killed=%s\n", kreason); fprintf(stderr, "Connection ID (thread ID): %lu\n", (ulong) thd->thread_id);
fprintf(stderr, "Status: %s\n", kreason);
fputc('\n', stderr);
} }
fprintf(stderr, "\ fprintf(stderr, "\
The manual page at http://dev.mysql.com/doc/mysql/en/crashing.html contains\n\ The manual page at http://dev.mysql.com/doc/mysql/en/crashing.html contains\n\
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment