Commit d7ee5bdc authored by Linus Torvalds's avatar Linus Torvalds
Browse files

Merge tag 'firewire-fixes-6.17-rc1' of...

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
parents 24ea63ea 0342273e
Loading
Loading
Loading
Loading
+81 −10
Original line number Diff line number Diff line
@@ -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,14 +981,54 @@ 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))
			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);
}
+4 −0
Original line number Diff line number Diff line
@@ -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 {