firewire fixes for v6.17-rc1

This set of fixes includes a solution for the issue described in the tag
 annotation for v6.17 updates.
 
 The issue involved a potential call to schedule() within an RCU read-side
 critical section. The solution applies reference counting to ensure that
 handlers which may call schedule() are invoked safely outside of the
 critical section.
 -----BEGIN PGP SIGNATURE-----
 
 iHUEABYKAB0WIQQE66IEYNDXNBPeGKSsLtaWM8LwEwUCaJ6SzAAKCRCsLtaWM8Lw
 E2YMAP0SNa3lrX9AWVviipIztqPb5Eo5jbMFk1KztEYWf/g0RQD+JxdELawys+3V
 oVNceJ5UUpQy0lSge4bll0Y1kZNlAQE=
 =gk22
 -----END PGP SIGNATURE-----

Merge tag 'firewire-fixes-6.17-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/ieee1394/linux1394

Pull firewire fixes from Takashi Sakamoto:
 "This fixes a potential call to schedule() within an RCU read-side
  critical section. The solution applies reference counting to ensure
  that handlers which may call schedule() are invoked safely outside of
  the critical section"

* tag 'firewire-fixes-6.17-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/ieee1394/linux1394:
  firewire: core: reallocate buffer for FCP address handlers when more than 4 are registered
  firewire: core: call FCP address handlers outside RCU read-side critical section
  firewire: core: call handler for exclusive regions outside RCU read-side critical section
  firewire: core: use reference counting to invoke address handlers safely
This commit is contained in:
Linus Torvalds 2025-08-14 19:15:22 -07:00
commit d7ee5bdce7
2 changed files with 85 additions and 10 deletions

View file

@ -550,6 +550,23 @@ const struct fw_address_region fw_unit_space_region =
{ .start = 0xfffff0000900ULL, .end = 0x1000000000000ULL, };
#endif /* 0 */
static void complete_address_handler(struct kref *kref)
{
struct fw_address_handler *handler = container_of(kref, struct fw_address_handler, kref);
complete(&handler->done);
}
static void get_address_handler(struct fw_address_handler *handler)
{
kref_get(&handler->kref);
}
static int put_address_handler(struct fw_address_handler *handler)
{
return kref_put(&handler->kref, complete_address_handler);
}
/**
* fw_core_add_address_handler() - register for incoming requests
* @handler: callback
@ -596,6 +613,8 @@ int fw_core_add_address_handler(struct fw_address_handler *handler,
if (other != NULL) {
handler->offset += other->length;
} else {
init_completion(&handler->done);
kref_init(&handler->kref);
list_add_tail_rcu(&handler->link, &address_handler_list);
ret = 0;
break;
@ -621,6 +640,9 @@ void fw_core_remove_address_handler(struct fw_address_handler *handler)
list_del_rcu(&handler->link);
synchronize_rcu();
if (!put_address_handler(handler))
wait_for_completion(&handler->done);
}
EXPORT_SYMBOL(fw_core_remove_address_handler);
@ -914,22 +936,31 @@ static void handle_exclusive_region_request(struct fw_card *card,
handler = lookup_enclosing_address_handler(&address_handler_list, offset,
request->length);
if (handler)
handler->address_callback(card, request, tcode, destination, source,
p->generation, offset, request->data,
request->length, handler->callback_data);
get_address_handler(handler);
}
if (!handler)
if (!handler) {
fw_send_response(card, request, RCODE_ADDRESS_ERROR);
return;
}
// Outside the RCU read-side critical section. Without spinlock. With reference count.
handler->address_callback(card, request, tcode, destination, source, p->generation, offset,
request->data, request->length, handler->callback_data);
put_address_handler(handler);
}
// To use kmalloc allocator efficiently, this should be power of two.
#define BUFFER_ON_KERNEL_STACK_SIZE 4
static void handle_fcp_region_request(struct fw_card *card,
struct fw_packet *p,
struct fw_request *request,
unsigned long long offset)
{
struct fw_address_handler *handler;
int tcode, destination, source;
struct fw_address_handler *buffer_on_kernel_stack[BUFFER_ON_KERNEL_STACK_SIZE];
struct fw_address_handler *handler, **handlers;
int tcode, destination, source, i, count, buffer_size;
if ((offset != (CSR_REGISTER_BASE | CSR_FCP_COMMAND) &&
offset != (CSR_REGISTER_BASE | CSR_FCP_RESPONSE)) ||
@ -950,15 +981,55 @@ static void handle_fcp_region_request(struct fw_card *card,
return;
}
count = 0;
handlers = buffer_on_kernel_stack;
buffer_size = ARRAY_SIZE(buffer_on_kernel_stack);
scoped_guard(rcu) {
list_for_each_entry_rcu(handler, &address_handler_list, link) {
if (is_enclosing_handler(handler, offset, request->length))
handler->address_callback(card, request, tcode, destination, source,
p->generation, offset, request->data,
request->length, handler->callback_data);
if (is_enclosing_handler(handler, offset, request->length)) {
if (count >= buffer_size) {
int next_size = buffer_size * 2;
struct fw_address_handler **buffer_on_kernel_heap;
if (handlers == buffer_on_kernel_stack)
buffer_on_kernel_heap = NULL;
else
buffer_on_kernel_heap = handlers;
buffer_on_kernel_heap =
krealloc_array(buffer_on_kernel_heap, next_size,
sizeof(*buffer_on_kernel_heap), GFP_ATOMIC);
// FCP is used for purposes unrelated to significant system
// resources (e.g. storage or networking), so allocation
// failures are not considered so critical.
if (!buffer_on_kernel_heap)
break;
if (handlers == buffer_on_kernel_stack) {
memcpy(buffer_on_kernel_heap, buffer_on_kernel_stack,
sizeof(buffer_on_kernel_stack));
}
handlers = buffer_on_kernel_heap;
buffer_size = next_size;
}
get_address_handler(handler);
handlers[count++] = handler;
}
}
}
for (i = 0; i < count; ++i) {
handler = handlers[i];
handler->address_callback(card, request, tcode, destination, source,
p->generation, offset, request->data,
request->length, handler->callback_data);
put_address_handler(handler);
}
if (handlers != buffer_on_kernel_stack)
kfree(handlers);
fw_send_response(card, request, RCODE_COMPLETE);
}

View file

@ -341,7 +341,11 @@ struct fw_address_handler {
u64 length;
fw_address_callback_t address_callback;
void *callback_data;
// Only for core functions.
struct list_head link;
struct kref kref;
struct completion done;
};
struct fw_address_region {