mirror of
https://github.com/systemd/systemd.git
synced 2026-08-09 17:38:42 +00:00
udev: clear event back-pointer when freeing a worker
worker_free() did not clear worker->event->worker, leaving an event
that was still attached to a worker pointing at freed memory once the
worker was gone. manager_free() frees the workers (hashmap_free())
before it walks manager->events to process them, and event_free() then
writes event->worker->event = NULL into the already-freed worker.
Clear the back-pointer in worker_free(), mirroring event_free() which
already clears the worker's pointer. The link used to be torn down by
the event_free(worker->event) call in worker_free(), which was dropped
when events became reference counted.
The Event and Worker definitions move to udev-manager.h (next to
Manager) so the unit test can construct and free a bound worker/event
pair.
Follow-up for cb16d47f30
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "tests.h"
|
||||
#include "udev-manager.h"
|
||||
|
||||
@@ -16,4 +17,22 @@ TEST(devpath_conflict) {
|
||||
"/devices/pci0000:00/0000:00:1c.4/0000:3c:00.0/nvme/nvme0/nvme0n1/nvme0n1p1"));
|
||||
}
|
||||
|
||||
TEST(worker_free_detaches_event) {
|
||||
/* worker_free() must clear the back-pointer of the event it is processing, otherwise the event is
|
||||
* left referencing the freed worker. This happens e.g. on manager_free(), which frees the workers
|
||||
* before the events still referencing them. */
|
||||
|
||||
Worker *worker = ASSERT_PTR(new0(Worker, 1));
|
||||
worker->pidref = PIDREF_NULL;
|
||||
|
||||
_cleanup_free_ Event *event = ASSERT_PTR(new0(Event, 1));
|
||||
|
||||
worker->event = event;
|
||||
event->worker = worker;
|
||||
|
||||
worker_free(worker);
|
||||
|
||||
ASSERT_NULL(event->worker);
|
||||
}
|
||||
|
||||
DEFINE_TEST_MAIN(LOG_DEBUG);
|
||||
|
||||
@@ -50,63 +50,6 @@
|
||||
#define EVENT_REQUEUE_INTERVAL_USEC (200 * USEC_PER_MSEC)
|
||||
#define EVENT_REQUEUE_TIMEOUT_USEC (3 * USEC_PER_MINUTE)
|
||||
|
||||
typedef enum EventState {
|
||||
EVENT_UNDEF,
|
||||
EVENT_QUEUED,
|
||||
EVENT_RUNNING,
|
||||
EVENT_LOCKED,
|
||||
EVENT_PROCESSED,
|
||||
} EventState;
|
||||
|
||||
typedef struct Event {
|
||||
/* All events that have not been processed (state != EVENT_PROCESSED) are referenced by the Manager.
|
||||
* Additionally, an event may be referenced by events blocked by this event. See event_find_blocker(). */
|
||||
unsigned n_ref;
|
||||
|
||||
Manager *manager;
|
||||
Worker *worker;
|
||||
EventState state;
|
||||
|
||||
sd_device *dev;
|
||||
|
||||
sd_device_action_t action;
|
||||
uint64_t seqnum;
|
||||
const char *id;
|
||||
const char *devpath;
|
||||
const char *devpath_old;
|
||||
const char *devnode;
|
||||
|
||||
/* Used when the device is locked by another program. */
|
||||
usec_t requeue_next_usec;
|
||||
usec_t requeue_timeout_usec;
|
||||
unsigned locked_event_prioq_index;
|
||||
char *whole_disk;
|
||||
LIST_FIELDS(Event, same_disk);
|
||||
|
||||
/* The last blocker for this event. This event must not be processed before the blocker is processed. */
|
||||
Event *blocker;
|
||||
|
||||
LIST_FIELDS(Event, event);
|
||||
} Event;
|
||||
|
||||
typedef enum WorkerState {
|
||||
WORKER_UNDEF,
|
||||
WORKER_RUNNING,
|
||||
WORKER_IDLE,
|
||||
WORKER_KILLED,
|
||||
} WorkerState;
|
||||
|
||||
typedef struct Worker {
|
||||
Manager *manager;
|
||||
PidRef pidref;
|
||||
sd_event_source *child_event_source;
|
||||
sd_event_source *timeout_warning_event_source;
|
||||
sd_event_source *timeout_kill_event_source;
|
||||
union sockaddr_union address;
|
||||
WorkerState state;
|
||||
Event *event;
|
||||
} Worker;
|
||||
|
||||
static void event_unset_whole_disk(Event *event) {
|
||||
Manager *manager = ASSERT_PTR(ASSERT_PTR(event)->manager);
|
||||
|
||||
@@ -171,13 +114,18 @@ static Event* event_enter_processed(Event *event) {
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(Event*, event_enter_processed);
|
||||
|
||||
static Worker* worker_free(Worker *worker) {
|
||||
Worker* worker_free(Worker *worker) {
|
||||
if (!worker)
|
||||
return NULL;
|
||||
|
||||
if (worker->manager)
|
||||
hashmap_remove(worker->manager->workers, &worker->pidref);
|
||||
|
||||
/* If an event is still attached, clear its back-pointer so it doesn't dangle (mirrors event_free(),
|
||||
* which clears the worker's pointer). */
|
||||
if (worker->event)
|
||||
worker->event->worker = NULL;
|
||||
|
||||
sd_event_source_disable_unref(worker->child_event_source);
|
||||
sd_event_source_unref(worker->timeout_warning_event_source);
|
||||
sd_event_source_unref(worker->timeout_kill_event_source);
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "sd-device.h"
|
||||
#include "sd-event.h"
|
||||
|
||||
#include "list.h"
|
||||
#include "pidref.h"
|
||||
#include "socket-util.h"
|
||||
#include "udev-config.h"
|
||||
#include "udev-forward.h"
|
||||
|
||||
@@ -76,10 +79,69 @@ typedef struct Manager {
|
||||
bool exit;
|
||||
} Manager;
|
||||
|
||||
typedef enum EventState {
|
||||
EVENT_UNDEF,
|
||||
EVENT_QUEUED,
|
||||
EVENT_RUNNING,
|
||||
EVENT_LOCKED,
|
||||
EVENT_PROCESSED,
|
||||
} EventState;
|
||||
|
||||
typedef struct Event {
|
||||
/* All events that have not been processed (state != EVENT_PROCESSED) are referenced by the Manager.
|
||||
* Additionally, an event may be referenced by events blocked by this event. See event_find_blocker(). */
|
||||
unsigned n_ref;
|
||||
|
||||
Manager *manager;
|
||||
Worker *worker;
|
||||
EventState state;
|
||||
|
||||
sd_device *dev;
|
||||
|
||||
sd_device_action_t action;
|
||||
uint64_t seqnum;
|
||||
const char *id;
|
||||
const char *devpath;
|
||||
const char *devpath_old;
|
||||
const char *devnode;
|
||||
|
||||
/* Used when the device is locked by another program. */
|
||||
usec_t requeue_next_usec;
|
||||
usec_t requeue_timeout_usec;
|
||||
unsigned locked_event_prioq_index;
|
||||
char *whole_disk;
|
||||
LIST_FIELDS(Event, same_disk);
|
||||
|
||||
/* The last blocker for this event. This event must not be processed before the blocker is processed. */
|
||||
Event *blocker;
|
||||
|
||||
LIST_FIELDS(Event, event);
|
||||
} Event;
|
||||
|
||||
typedef enum WorkerState {
|
||||
WORKER_UNDEF,
|
||||
WORKER_RUNNING,
|
||||
WORKER_IDLE,
|
||||
WORKER_KILLED,
|
||||
} WorkerState;
|
||||
|
||||
typedef struct Worker {
|
||||
Manager *manager;
|
||||
PidRef pidref;
|
||||
sd_event_source *child_event_source;
|
||||
sd_event_source *timeout_warning_event_source;
|
||||
sd_event_source *timeout_kill_event_source;
|
||||
union sockaddr_union address;
|
||||
WorkerState state;
|
||||
Event *event;
|
||||
} Worker;
|
||||
|
||||
Manager* manager_new(void);
|
||||
Manager* manager_free(Manager *manager);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free);
|
||||
|
||||
Worker* worker_free(Worker *worker);
|
||||
|
||||
int manager_main(Manager *manager);
|
||||
void manager_reload(Manager *manager, bool force);
|
||||
void manager_revert(Manager *manager);
|
||||
|
||||
Reference in New Issue
Block a user