user44789
user44789

Reputation: 113

mysql_stmt_free_result segfaults on a successfully executed query

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

Answers (1)

user7994388
user7994388

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

Related Questions