Reputation: 113
Im using the MySQL C API to communicate with my database. I'm currently migrating to using prepared statements. However mysql_stmt_free_result
causes a segfault and I can't figure out why.
#define SENSOR_SQL_GET_KEY "SELECT `password` FROM `station` " \
"WHERE `station_id`=?;"
int32_t sensor_get_station_key(struct Sensor_Handle *handle, char *key, uint32_t size,
uint32_t station_id)
{
MYSQL_BIND bind;
uint32_t length;
MYSQL_STMT *stmt = handle->stmt_get_key;
memset(&bind,0,sizeof(bind));
bind.buffer_type=MYSQL_TYPE_LONG;
bind.buffer=&station_id;
bind.is_unsigned = 1;
check(mysql_stmt_bind_param(stmt, &bind)==0, "mysql_stmt_bind_param failed: %s",
mysql_stmt_error(stmt));
check(mysql_stmt_execute(stmt)==0, "mysql_stmt_execute failed: %s",
mysql_stmt_error(stmt));
memset(&bind,0,sizeof(bind));
bind.buffer_type=MYSQL_TYPE_STRING;
bind.buffer=key;
bind.buffer_length=size;
bind.length=(unsigned long*)&length;
check(mysql_stmt_bind_result(stmt, &bind)==0, "mysql_stmt_bind_result failed");
check(mysql_stmt_store_result(stmt)==0, "mysql_stmt_store_result failed: %s",
mysql_stmt_error(stmt));
check(mysql_stmt_num_rows(stmt)==1,
"mysql_stmt_num_rows didn't return 1, for station_id %i", station_id);
check(mysql_stmt_fetch(stmt)==0,
"mysql_stmt_fetch_result failed: %s", mysql_stmt_error(stmt));
mysql_stmt_free_result(stmt); //segfaults
return length;
error:
mysql_stmt_free_result(handle->stmt_get_key);
return -1;
}
I've marked the line that causes the segfault (obtained using gdb backtrace). If I comment that line out everything works fine and key
(a 32 byte array on the stack declared in the calling function) is filled with the correct 32byte string from the database. So the query does execute successfully.
In the following code on the other hand mysql_stmt_free_result
executes without causing a segfault which leaves me puzzled due to the very similar structure of the code.
#define SENSOR_SQL_CHECK_SENSOR_STATION "SELECT `station_id` FROM `sensor` " \
"WHERE sensor_id = ?"
int32_t sensor_check_sensor_station(struct Sensor_Handle *handle, uint32_t sensor_id, uint32_t station_id)
{
MYSQL_BIND bind;
uint32_t id;
MYSQL_STMT *stmt = handle->stmt_check_sensor_station;
memset(&bind,0,sizeof(bind));
bind.buffer_type=MYSQL_TYPE_LONG;
bind.buffer=&sensor_id;
bind.is_unsigned = 1;
check(mysql_stmt_bind_param(stmt, &bind)==0,
"mysql_stmt_bind_param failed: %s", mysql_stmt_error(stmt));
check(mysql_stmt_execute(stmt)==0,
"mysql_stmt_execute failed: %s", mysql_stmt_error(stmt));
check(mysql_stmt_store_result(stmt)==0,
"mysql_stmt_store_result failed: %s", mysql_stmt_error(stmt));
if(!mysql_stmt_num_rows(stmt)) //sensor_id doesn't exist
{
mysql_stmt_free_result(stmt);
return 0;
}
memset(&bind,0,sizeof(bind));
bind.buffer_type=MYSQL_TYPE_LONG;
bind.buffer=&id;
bind.is_unsigned=1;
check(mysql_stmt_bind_result(stmt, &bind)==0, "mysql_stmt_bind_result failed");
check(mysql_stmt_fetch(stmt)==0,
"mysql_stmt_fetch_result failed: %s", mysql_stmt_error(stmt));
mysql_stmt_free_result(stmt); //no problem
if(id == station_id)
return 1;
else
return 0;
error:
return -1;
}
I also used valgrind memcheck on the code which did not report any memory access errors. I've included the defines with the actual queries that are used to prepare the statements in a separate function.
More Info:
I store my prepared statements in
struct Sensor_Handle
{
MYSQL *connection;
MYSQL_STMT *stmt_get_key;
MYSQL_STMT *stmt_check_sensor_station;
etc.
};
and I prepare them like this
int32_t sensor_create_statement(struct Sensor_Handle *handle, MYSQL_STMT **stmt_out, char *sql)
{
MYSQL_STMT *stmt;
stmt = mysql_stmt_init(handle->connection);
check(stmt, "mysql_stmt_init failed: Out of Memory");
check(mysql_stmt_prepare(stmt, sql, strlen(sql))==0,
"mysql_stmt_prepare failed: %s", mysql_stmt_error(stmt));
*stmt_out = stmt;
return 0;
error:
return -1;
}
int32_t sensor_statement_init(struct Sensor_Handle *handle)
{
MYSQL_STMT *stmt;
//sensor_get_station_key
check(sensor_create_statement(handle, &stmt, SENSOR_SQL_GET_KEY)==0,
"sensor_create_statement failed");
handle->stmt_get_key = stmt;
//sensor_check_sensor_station
check(sensor_create_statement(handle, &stmt, SENSOR_SQL_CHECK_SENSOR_STATION)==0,
"sensor_create_statement failed");
handle->stmt_check_sensor_station = stmt;
return 0;
error:
return -1;
}
The function that segfaults is called in another function like this:
char key[AES_KEY_SIZE];
i=sensor_get_station_key(handle, key, sizeof(key), station_id);
check(i==AES_KEY_SIZE,
"sensor_get_station_id didn't return the expected keysize. Got: %i, expected: %i",
i, AES_KEY_SIZE);
So as you can see it even returns the expected keysize if I remove the mysql_stmt_free_result
Upvotes: 0
Views: 475
Reputation: 473
uint32_t length;
...
bind.length=(unsigned long*)&length;
This isn't safe, bind.length is declared as an unsigned long pointer but you're passing the address of a 32 bit uint32_t. An unsigned long could be 64 bits, if it is you end up with a stack corruption when mysql lib writes to it.
Upvotes: 1