bluez/gobex/gobex-transfer.c
Denis Grigorev 7a35c12a6c gobex: Fix segfault caused by interrupted transfer
When a obex transfer is interrupted by a peer in the middle, the response
G_OBEX_RSP_FORBIDDEN comes and the transfer is freed in transfer_complete.
However gobex is still ref'ed and gobex->io continues to be writable,
so write_data() and then g_obex_abort() are called. When the abort response
comes, struct obc_transfer is already freed, which leads to the crash.

Backtrace :
__GI___pthread_mutex_lock (mutex=0x65732f74) at pthread_mutex_lock.c:67
0xecc6eeda in dbus_connection_get_object_path_data () from libdbus-1.so.3
0x000457d4 in g_dbus_emit_property_changed_full () at gdbus/object.c:1794
0x00045868 in g_dbus_emit_property_changed () at gdbus/object.c:1832
0x000367f0 in transfer_set_status () at obexd/client/transfer.c:211
0x0003681e in transfer_set_status () at obexd/client/transfer.c:206
xfer_complete () at obexd/client/transfer.c:672
0x00022df6 in transfer_complete () at gobex/gobex-transfer.c:103
0x00022f44 in transfer_abort_response () at gobex/gobex-transfer.c:124
0x00020a0e in handle_response () at gobex/gobex.c:1128
0x00020dde in incoming_data () at gobex/gobex.c:1373

This commit introduces g_obex_drop_tx_queue(), which will be called if
a transfer error detected. After the tx queue is dropped, obex shuts
down gracefully.
2020-07-01 10:49:49 -07:00

676 lines
16 KiB
C

/*
*
* OBEX library with GLib integration
*
* Copyright (C) 2011 Intel Corporation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
#include <errno.h>
#include "gobex/gobex.h"
#include "gobex/gobex-debug.h"
#define FIRST_PACKET_TIMEOUT 60
static GSList *transfers = NULL;
static void transfer_response(GObex *obex, GError *err, GObexPacket *rsp,
gpointer user_data);
struct transfer {
guint id;
guint8 opcode;
GObex *obex;
guint req_id;
guint put_id;
guint get_id;
guint abort_id;
GObexDataProducer data_producer;
GObexDataConsumer data_consumer;
GObexFunc complete_func;
gpointer user_data;
};
static void transfer_free(struct transfer *transfer)
{
g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", transfer->id);
transfers = g_slist_remove(transfers, transfer);
if (transfer->req_id > 0)
g_obex_cancel_req(transfer->obex, transfer->req_id, TRUE);
if (transfer->put_id > 0)
g_obex_remove_request_function(transfer->obex,
transfer->put_id);
if (transfer->get_id > 0)
g_obex_remove_request_function(transfer->obex,
transfer->get_id);
if (transfer->abort_id > 0)
g_obex_remove_request_function(transfer->obex,
transfer->abort_id);
g_obex_unref(transfer->obex);
g_free(transfer);
}
static struct transfer *find_transfer(guint id)
{
GSList *l;
for (l = transfers; l != NULL; l = g_slist_next(l)) {
struct transfer *t = l->data;
if (t->id == id)
return t;
}
return NULL;
}
static void transfer_complete(struct transfer *transfer, GError *err)
{
guint id = transfer->id;
g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", id);
if (err) {
/* No further tx must be performed */
g_obex_drop_tx_queue(transfer->obex);
}
transfer->complete_func(transfer->obex, err, transfer->user_data);
/* Check if the complete_func removed the transfer */
if (find_transfer(id) == NULL)
return;
transfer_free(transfer);
}
static void transfer_abort_response(GObex *obex, GError *err, GObexPacket *rsp,
gpointer user_data)
{
struct transfer *transfer = user_data;
g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", transfer->id);
transfer->req_id = 0;
/* Intentionally override error */
err = g_error_new(G_OBEX_ERROR, G_OBEX_ERROR_CANCELLED,
"Operation was aborted");
g_obex_debug(G_OBEX_DEBUG_ERROR, "%s", err->message);
transfer_complete(transfer, err);
g_error_free(err);
}
static gssize put_get_data(void *buf, gsize len, gpointer user_data)
{
struct transfer *transfer = user_data;
GObexPacket *req;
GError *err = NULL;
gssize ret;
ret = transfer->data_producer(buf, len, transfer->user_data);
if (ret == 0 || ret == -EAGAIN)
return ret;
if (ret > 0) {
/* Check if SRM is active */
if (!g_obex_srm_active(transfer->obex))
return ret;
/* Generate next packet */
req = g_obex_packet_new(transfer->opcode, FALSE,
G_OBEX_HDR_INVALID);
g_obex_packet_add_body(req, put_get_data, transfer);
transfer->req_id = g_obex_send_req(transfer->obex, req, -1,
transfer_response, transfer,
&err);
goto done;
}
transfer->req_id = g_obex_abort(transfer->obex, transfer_abort_response,
transfer, &err);
done:
if (err != NULL) {
transfer_complete(transfer, err);
g_error_free(err);
}
return ret;
}
static gboolean handle_get_body(struct transfer *transfer, GObexPacket *rsp,
GError **err)
{
GObexHeader *body = g_obex_packet_get_body(rsp);
gboolean ret;
const guint8 *buf;
gsize len;
if (body == NULL)
return TRUE;
g_obex_header_get_bytes(body, &buf, &len);
if (len == 0)
return TRUE;
ret = transfer->data_consumer(buf, len, transfer->user_data);
if (ret == FALSE)
g_set_error(err, G_OBEX_ERROR, G_OBEX_ERROR_CANCELLED,
"Data consumer callback failed");
return ret;
}
static void transfer_response(GObex *obex, GError *err, GObexPacket *rsp,
gpointer user_data)
{
struct transfer *transfer = user_data;
GObexPacket *req;
gboolean rspcode, final;
guint id;
g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", transfer->id);
id = transfer->req_id;
transfer->req_id = 0;
if (err != NULL) {
transfer_complete(transfer, err);
return;
}
rspcode = g_obex_packet_get_operation(rsp, &final);
if (rspcode != G_OBEX_RSP_SUCCESS && rspcode != G_OBEX_RSP_CONTINUE) {
err = g_error_new(G_OBEX_ERROR, rspcode, "%s",
g_obex_strerror(rspcode));
goto failed;
}
if (transfer->opcode == G_OBEX_OP_GET) {
handle_get_body(transfer, rsp, &err);
if (err != NULL)
goto failed;
}
if (rspcode == G_OBEX_RSP_SUCCESS) {
transfer_complete(transfer, NULL);
return;
}
if (transfer->opcode == G_OBEX_OP_PUT) {
req = g_obex_packet_new(transfer->opcode, FALSE,
G_OBEX_HDR_INVALID);
g_obex_packet_add_body(req, put_get_data, transfer);
} else if (!g_obex_srm_active(transfer->obex)) {
req = g_obex_packet_new(transfer->opcode, TRUE,
G_OBEX_HDR_INVALID);
} else {
/* Keep id since request still outstanting */
transfer->req_id = id;
return;
}
transfer->req_id = g_obex_send_req(obex, req, -1, transfer_response,
transfer, &err);
failed:
if (err != NULL) {
g_obex_debug(G_OBEX_DEBUG_ERROR, "%s", err->message);
transfer_complete(transfer, err);
g_error_free(err);
}
}
static struct transfer *transfer_new(GObex *obex, guint8 opcode,
GObexFunc complete_func, gpointer user_data)
{
static guint next_id = 1;
struct transfer *transfer;
g_obex_debug(G_OBEX_DEBUG_TRANSFER, "obex %p opcode %u", obex, opcode);
transfer = g_new0(struct transfer, 1);
transfer->id = next_id++;
transfer->opcode = opcode;
transfer->obex = g_obex_ref(obex);
transfer->complete_func = complete_func;
transfer->user_data = user_data;
transfers = g_slist_append(transfers, transfer);
return transfer;
}
guint g_obex_put_req_pkt(GObex *obex, GObexPacket *req,
GObexDataProducer data_func, GObexFunc complete_func,
gpointer user_data, GError **err)
{
struct transfer *transfer;
g_obex_debug(G_OBEX_DEBUG_TRANSFER, "obex %p", obex);
if (g_obex_packet_get_operation(req, NULL) != G_OBEX_OP_PUT)
return 0;
transfer = transfer_new(obex, G_OBEX_OP_PUT, complete_func, user_data);
transfer->data_producer = data_func;
g_obex_packet_add_body(req, put_get_data, transfer);
transfer->req_id = g_obex_send_req(obex, req, FIRST_PACKET_TIMEOUT,
transfer_response, transfer, err);
if (transfer->req_id == 0) {
transfer_free(transfer);
return 0;
}
g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", transfer->id);
return transfer->id;
}
guint g_obex_put_req(GObex *obex, GObexDataProducer data_func,
GObexFunc complete_func, gpointer user_data,
GError **err, guint first_hdr_id, ...)
{
GObexPacket *req;
va_list args;
g_obex_debug(G_OBEX_DEBUG_TRANSFER, "obex %p", obex);
va_start(args, first_hdr_id);
req = g_obex_packet_new_valist(G_OBEX_OP_PUT, FALSE,
first_hdr_id, args);
va_end(args);
return g_obex_put_req_pkt(obex, req, data_func, complete_func,
user_data, err);
}
static void transfer_abort_req(GObex *obex, GObexPacket *req, gpointer user_data)
{
struct transfer *transfer = user_data;
GObexPacket *rsp;
GError *err;
g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", transfer->id);
err = g_error_new(G_OBEX_ERROR, G_OBEX_ERROR_CANCELLED,
"Request was aborted");
rsp = g_obex_packet_new(G_OBEX_RSP_SUCCESS, TRUE, G_OBEX_HDR_INVALID);
g_obex_send(obex, rsp, NULL);
transfer_complete(transfer, err);
g_error_free(err);
}
static guint8 put_get_bytes(struct transfer *transfer, GObexPacket *req)
{
GObexHeader *body;
gboolean final;
guint8 rsp;
const guint8 *buf;
gsize len;
g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", transfer->id);
g_obex_packet_get_operation(req, &final);
if (final)
rsp = G_OBEX_RSP_SUCCESS;
else
rsp = G_OBEX_RSP_CONTINUE;
body = g_obex_packet_get_body(req);
if (body == NULL)
return rsp;
g_obex_header_get_bytes(body, &buf, &len);
if (len == 0)
return rsp;
if (transfer->data_consumer(buf, len, transfer->user_data) == FALSE)
rsp = G_OBEX_RSP_FORBIDDEN;
return rsp;
}
static void transfer_put_req_first(struct transfer *transfer, GObexPacket *req,
guint8 first_hdr_id, va_list args)
{
GError *err = NULL;
GObexPacket *rsp;
guint8 rspcode;
g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", transfer->id);
rspcode = put_get_bytes(transfer, req);
rsp = g_obex_packet_new_valist(rspcode, TRUE, first_hdr_id, args);
if (!g_obex_send(transfer->obex, rsp, &err)) {
transfer_complete(transfer, err);
g_error_free(err);
return;
}
if (rspcode != G_OBEX_RSP_CONTINUE)
transfer_complete(transfer, NULL);
}
static void transfer_put_req(GObex *obex, GObexPacket *req, gpointer user_data)
{
struct transfer *transfer = user_data;
GError *err = NULL;
GObexPacket *rsp;
guint8 rspcode;
g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", transfer->id);
rspcode = put_get_bytes(transfer, req);
/* Don't send continue while SRM is active */
if (g_obex_srm_active(transfer->obex) &&
rspcode == G_OBEX_RSP_CONTINUE)
goto done;
rsp = g_obex_packet_new(rspcode, TRUE, G_OBEX_HDR_INVALID);
if (!g_obex_send(obex, rsp, &err)) {
transfer_complete(transfer, err);
g_error_free(err);
return;
}
done:
if (rspcode != G_OBEX_RSP_CONTINUE)
transfer_complete(transfer, NULL);
}
guint g_obex_put_rsp(GObex *obex, GObexPacket *req,
GObexDataConsumer data_func, GObexFunc complete_func,
gpointer user_data, GError **err,
guint first_hdr_id, ...)
{
struct transfer *transfer;
va_list args;
guint id;
g_obex_debug(G_OBEX_DEBUG_TRANSFER, "obex %p", obex);
transfer = transfer_new(obex, G_OBEX_OP_PUT, complete_func, user_data);
transfer->data_consumer = data_func;
va_start(args, first_hdr_id);
transfer_put_req_first(transfer, req, first_hdr_id, args);
va_end(args);
if (!g_slist_find(transfers, transfer))
return 0;
id = g_obex_add_request_function(obex, G_OBEX_OP_PUT, transfer_put_req,
transfer);
transfer->put_id = id;
id = g_obex_add_request_function(obex, G_OBEX_OP_ABORT,
transfer_abort_req, transfer);
transfer->abort_id = id;
g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", transfer->id);
return transfer->id;
}
guint g_obex_get_req_pkt(GObex *obex, GObexPacket *req,
GObexDataConsumer data_func, GObexFunc complete_func,
gpointer user_data, GError **err)
{
struct transfer *transfer;
g_obex_debug(G_OBEX_DEBUG_TRANSFER, "obex %p", obex);
if (g_obex_packet_get_operation(req, NULL) != G_OBEX_OP_GET)
return 0;
transfer = transfer_new(obex, G_OBEX_OP_GET, complete_func, user_data);
transfer->data_consumer = data_func;
transfer->req_id = g_obex_send_req(obex, req, FIRST_PACKET_TIMEOUT,
transfer_response, transfer, err);
if (transfer->req_id == 0) {
transfer_free(transfer);
return 0;
}
g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", transfer->id);
return transfer->id;
}
guint g_obex_get_req(GObex *obex, GObexDataConsumer data_func,
GObexFunc complete_func, gpointer user_data,
GError **err, guint first_hdr_id, ...)
{
struct transfer *transfer;
GObexPacket *req;
va_list args;
g_obex_debug(G_OBEX_DEBUG_TRANSFER, "obex %p", obex);
transfer = transfer_new(obex, G_OBEX_OP_GET, complete_func, user_data);
transfer->data_consumer = data_func;
va_start(args, first_hdr_id);
req = g_obex_packet_new_valist(G_OBEX_OP_GET, TRUE,
first_hdr_id, args);
va_end(args);
transfer->req_id = g_obex_send_req(obex, req, FIRST_PACKET_TIMEOUT,
transfer_response, transfer, err);
if (transfer->req_id == 0) {
transfer_free(transfer);
return 0;
}
g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", transfer->id);
return transfer->id;
}
static gssize get_get_data(void *buf, gsize len, gpointer user_data)
{
struct transfer *transfer = user_data;
GObexPacket *req, *rsp;
GError *err = NULL;
gssize ret;
guint8 op;
g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", transfer->id);
ret = transfer->data_producer(buf, len, transfer->user_data);
if (ret > 0) {
if (!g_obex_srm_active(transfer->obex))
return ret;
/* Generate next response */
rsp = g_obex_packet_new(G_OBEX_RSP_CONTINUE, TRUE,
G_OBEX_HDR_INVALID);
g_obex_packet_add_body(rsp, get_get_data, transfer);
if (!g_obex_send(transfer->obex, rsp, &err)) {
transfer_complete(transfer, err);
g_error_free(err);
}
return ret;
}
if (ret == -EAGAIN)
return ret;
if (ret == 0) {
transfer_complete(transfer, NULL);
return ret;
}
op = g_obex_errno_to_rsp(ret);
req = g_obex_packet_new(op, TRUE, G_OBEX_HDR_INVALID);
g_obex_send(transfer->obex, req, NULL);
err = g_error_new(G_OBEX_ERROR, G_OBEX_ERROR_CANCELLED,
"Data producer function failed");
g_obex_debug(G_OBEX_DEBUG_ERROR, "%s", err->message);
transfer_complete(transfer, err);
g_error_free(err);
return ret;
}
static gboolean transfer_get_req_first(struct transfer *transfer,
GObexPacket *rsp)
{
GError *err = NULL;
g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", transfer->id);
g_obex_packet_add_body(rsp, get_get_data, transfer);
if (!g_obex_send(transfer->obex, rsp, &err)) {
transfer_complete(transfer, err);
g_error_free(err);
return FALSE;
}
return TRUE;
}
static void transfer_get_req(GObex *obex, GObexPacket *req, gpointer user_data)
{
struct transfer *transfer = user_data;
GError *err = NULL;
GObexPacket *rsp;
g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", transfer->id);
rsp = g_obex_packet_new(G_OBEX_RSP_CONTINUE, TRUE, G_OBEX_HDR_INVALID);
g_obex_packet_add_body(rsp, get_get_data, transfer);
if (!g_obex_send(obex, rsp, &err)) {
transfer_complete(transfer, err);
g_error_free(err);
}
}
guint g_obex_get_rsp_pkt(GObex *obex, GObexPacket *rsp,
GObexDataProducer data_func, GObexFunc complete_func,
gpointer user_data, GError **err)
{
struct transfer *transfer;
guint id;
g_obex_debug(G_OBEX_DEBUG_TRANSFER, "obex %p", obex);
transfer = transfer_new(obex, G_OBEX_OP_GET, complete_func, user_data);
transfer->data_producer = data_func;
if (!transfer_get_req_first(transfer, rsp))
return 0;
if (!g_slist_find(transfers, transfer))
return 0;
id = g_obex_add_request_function(obex, G_OBEX_OP_GET, transfer_get_req,
transfer);
transfer->get_id = id;
id = g_obex_add_request_function(obex, G_OBEX_OP_ABORT,
transfer_abort_req, transfer);
transfer->abort_id = id;
g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", transfer->id);
return transfer->id;
}
guint g_obex_get_rsp(GObex *obex, GObexDataProducer data_func,
GObexFunc complete_func, gpointer user_data,
GError **err, guint first_hdr_id, ...)
{
GObexPacket *rsp;
va_list args;
g_obex_debug(G_OBEX_DEBUG_TRANSFER, "obex %p", obex);
va_start(args, first_hdr_id);
rsp = g_obex_packet_new_valist(G_OBEX_RSP_CONTINUE, TRUE,
first_hdr_id, args);
va_end(args);
return g_obex_get_rsp_pkt(obex, rsp, data_func, complete_func,
user_data, err);
}
gboolean g_obex_cancel_transfer(guint id, GObexFunc complete_func,
gpointer user_data)
{
struct transfer *transfer = NULL;
gboolean ret = TRUE;
g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", id);
transfer = find_transfer(id);
if (transfer == NULL)
return FALSE;
if (complete_func == NULL)
goto done;
transfer->complete_func = complete_func;
transfer->user_data = user_data;
if (!transfer->req_id) {
transfer->req_id = g_obex_abort(transfer->obex,
transfer_abort_response,
transfer, NULL);
if (transfer->req_id)
return TRUE;
}
ret = g_obex_cancel_req(transfer->obex, transfer->req_id, FALSE);
if (ret)
return TRUE;
done:
transfer_free(transfer);
return ret;
}