General flow when the issue occurs:
1. buf is allocated on prep_pool in prep_write_cb: data->buf = net_buf_alloc(&prep_pool, K_NO_WAIT);
2. buf is put to prep_queue in att_prep_write_rsp: net_buf_slist_put(&chan->att->prep_queue, data.buf);
3. buf is gotten from prep_queue in att_exec_write_rsp: buf = net_buf_slist_get(&chan->att->prep_queue);
4. exec_write_reassemble returns BT_ATT_ERR_INVALID_ATTRIBUTE_LEN because reassembled_data size isn't enough:
if (buf->len + entry->len > buf->size) {
return BT_ATT_ERR_INVALID_ATTRIBUTE_LEN;
}
5. att_exec_write_rsp terminates prematurely without net_buf_unref:
if (err != BT_ATT_ERR_SUCCESS) {
send_err_rsp(chan, BT_ATT_OP_EXEC_WRITE_REQ,
handle, err);
return 0;
}
As a result, at the next long write attempts, the prep_pool has only free CONFIG_BT_ATT_PREPARE_COUNT - 1 slots.
Even disconnecting doesn't help, because in att_reset only the entries from prep_queue do unref, but the buf was already put out of prep_queue.
Workaround I've implemented - check offset and len parameters in the application bt_gatt_attr_write_func_t callback and return BT_ATT_ERR_INVALID_OFFSET (instead of 0) if offset + len > attribute length.
In this case, buf in prep_write_cb isn't allocated, and then on att_exec_write_rsp with flag BT_ATT_FLAG_CANCEL all allocated bufs are unref successfully.