From 2573d91be862ed65781c4a37dcaaa051a905a48a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20B=C3=B6hmwalder?= Date: Wed, 5 Oct 2022 13:36:18 +0200 Subject: [PATCH] drbd: fix use-after-free bugs in get_initial_state See upstream commit aadb22ba2f65 ("drbd: Fix five use after free bugs in get_initial_state"). Original message: In get_initial_state, it calls notify_initial_state_done(skb,..) if cb->args[5]==1. If genlmsg_put() failed in notify_initial_state_done(), the skb will be freed by nlmsg_free(skb). Then get_initial_state will goto out and the freed skb will be used by return value skb->len, which is a uaf bug. What's worse, the same problem goes even further: skb can also be freed in the notify_*_state_change -> notify_*_state calls below. Thus 4 additional uaf bugs happened. My patch lets the problem callee functions: notify_initial_state_done and notify_*_state_change return an error code if errors happen. So that the error codes could be propagated and the uaf bugs can be avoid. --- drbd/drbd_int.h | 10 ++++---- drbd/drbd_nl.c | 51 ++++++++++++++++++++++++---------------- drbd/drbd_state.c | 18 +++++++------- drbd/drbd_state_change.h | 8 +++---- 4 files changed, 49 insertions(+), 38 deletions(-) diff --git a/drbd/drbd_int.h b/drbd/drbd_int.h index fcde3b96bd6a..4bf2c6dde93d 100644 --- a/drbd/drbd_int.h +++ b/drbd/drbd_int.h @@ -2154,30 +2154,30 @@ extern int drbd_al_initialize(struct drbd_device *, void *); extern struct mutex notification_mutex; extern atomic_t drbd_genl_seq; -extern void notify_resource_state(struct sk_buff *, +extern int notify_resource_state(struct sk_buff *, unsigned int, struct drbd_resource *, struct resource_info *, struct rename_resource_info *, enum drbd_notification_type); -extern void notify_device_state(struct sk_buff *, +extern int notify_device_state(struct sk_buff *, unsigned int, struct drbd_device *, struct device_info *, enum drbd_notification_type); -extern void notify_connection_state(struct sk_buff *, +extern int notify_connection_state(struct sk_buff *, unsigned int, struct drbd_connection *, struct connection_info *, enum drbd_notification_type); -extern void notify_peer_device_state(struct sk_buff *, +extern int notify_peer_device_state(struct sk_buff *, unsigned int, struct drbd_peer_device *, struct peer_device_info *, enum drbd_notification_type); extern void notify_helper(enum drbd_notification_type, struct drbd_device *, struct drbd_connection *, const char *, int); -extern void notify_path(struct drbd_connection *, struct drbd_path *, +extern int notify_path(struct drbd_connection *, struct drbd_path *, enum drbd_notification_type); extern void drbd_broadcast_peer_device_state(struct drbd_peer_device *); diff --git a/drbd/drbd_nl.c b/drbd/drbd_nl.c index 3dee5a1ab2eb..4418d143e09f 100644 --- a/drbd/drbd_nl.c +++ b/drbd/drbd_nl.c @@ -6492,7 +6492,7 @@ static int nla_put_notification_header(struct sk_buff *msg, return drbd_notification_header_to_skb(msg, &nh, true); } -void notify_resource_state(struct sk_buff *skb, +int notify_resource_state(struct sk_buff *skb, unsigned int seq, struct drbd_resource *resource, struct resource_info *resource_info, @@ -6546,16 +6546,17 @@ void notify_resource_state(struct sk_buff *skb, if (err && err != -ESRCH) goto failed; } - return; + return 0; nla_put_failure: nlmsg_free(skb); failed: drbd_err(resource, "Error %d while broadcasting event. Event seq:%u\n", err, seq); + return err; } -void notify_device_state(struct sk_buff *skb, +int notify_device_state(struct sk_buff *skb, unsigned int seq, struct drbd_device *device, struct device_info *device_info, @@ -6595,17 +6596,18 @@ void notify_device_state(struct sk_buff *skb, if (err && err != -ESRCH) goto failed; } - return; + return 0; nla_put_failure: nlmsg_free(skb); failed: drbd_err(device, "Error %d while broadcasting event. Event seq:%u\n", err, seq); + return err; } /* open coded path_parms_to_skb() iterating of the list */ -void notify_connection_state(struct sk_buff *skb, +int notify_connection_state(struct sk_buff *skb, unsigned int seq, struct drbd_connection *connection, struct connection_info *connection_info, @@ -6646,16 +6648,17 @@ void notify_connection_state(struct sk_buff *skb, if (err && err != -ESRCH) goto failed; } - return; + return 0; nla_put_failure: nlmsg_free(skb); failed: drbd_err(connection, "Error %d while broadcasting event. Event seq:%u\n", err, seq); + return err; } -void notify_peer_device_state(struct sk_buff *skb, +int notify_peer_device_state(struct sk_buff *skb, unsigned int seq, struct drbd_peer_device *peer_device, struct peer_device_info *peer_device_info, @@ -6696,13 +6699,14 @@ void notify_peer_device_state(struct sk_buff *skb, if (err && err != -ESRCH) goto failed; } - return; + return 0; nla_put_failure: nlmsg_free(skb); failed: drbd_err(peer_device, "Error %d while broadcasting event. Event seq:%u\n", err, seq); + return err; } void drbd_broadcast_peer_device_state(struct drbd_peer_device *peer_device) @@ -6714,7 +6718,7 @@ void drbd_broadcast_peer_device_state(struct drbd_peer_device *peer_device) mutex_unlock(¬ification_mutex); } -void notify_path_state(struct sk_buff *skb, +int notify_path_state(struct sk_buff *skb, unsigned int seq, /* until we have a backpointer in drbd_path, we need an explicit connection: */ struct drbd_connection *connection, @@ -6754,7 +6758,7 @@ void notify_path_state(struct sk_buff *skb, if (err && err != -ESRCH) goto failed; } - return; + return 0; nla_put_failure: nlmsg_free(skb); @@ -6762,16 +6766,19 @@ failed: /* FIXME add path specifics to our drbd_polymorph_printk.h */ drbd_err(connection, "path: Error %d while broadcasting event. Event seq:%u\n", err, seq); + return err; } -void notify_path(struct drbd_connection *connection, struct drbd_path *path, enum drbd_notification_type type) +int notify_path(struct drbd_connection *connection, struct drbd_path *path, enum drbd_notification_type type) { struct drbd_path_info path_info; + int err; path_info.path_established = path->established; mutex_lock(¬ification_mutex); - notify_path_state(NULL, 0, connection, path, &path_info, type); + err = notify_path_state(NULL, 0, connection, path, &path_info, type); mutex_unlock(¬ification_mutex); + return err; } @@ -6823,7 +6830,7 @@ fail: err, seq); } -static void notify_initial_state_done(struct sk_buff *skb, unsigned int seq) +static int notify_initial_state_done(struct sk_buff *skb, unsigned int seq) { struct drbd_genlmsghdr *dh; int err; @@ -6837,11 +6844,12 @@ static void notify_initial_state_done(struct sk_buff *skb, unsigned int seq) if (nla_put_notification_header(skb, NOTIFY_EXISTS)) goto nla_put_failure; genlmsg_end(skb, dh); - return; + return 0; nla_put_failure: nlmsg_free(skb); pr_err("Error %d sending event. Event seq:%u\n", err, seq); + return err; } static void free_state_changes(struct list_head *list) @@ -6869,6 +6877,7 @@ static int get_initial_state(struct sk_buff *skb, struct netlink_callback *cb) unsigned int seq = cb->args[2]; unsigned int n; enum drbd_notification_type flags = 0; + int err = 0; /* There is no need for taking notification_mutex here: it doesn't matter if the initial state events mix with later state change @@ -6877,20 +6886,20 @@ static int get_initial_state(struct sk_buff *skb, struct netlink_callback *cb) cb->args[5]--; if (cb->args[5] == 1) { - notify_initial_state_done(skb, seq); + err = notify_initial_state_done(skb, seq); goto out; } n = cb->args[4]++; if (cb->args[4] < cb->args[3]) flags |= NOTIFY_CONTINUES; if (n < 1) { - notify_resource_state_change(skb, seq, state_change, + err = notify_resource_state_change(skb, seq, state_change, NOTIFY_EXISTS | flags); goto next; } n--; if (n < state_change->n_connections) { - notify_connection_state_change(skb, seq, &state_change->connections[n], + err = notify_connection_state_change(skb, seq, &state_change->connections[n], NOTIFY_EXISTS | flags); goto next; } @@ -6900,7 +6909,7 @@ static int get_initial_state(struct sk_buff *skb, struct netlink_callback *cb) struct drbd_path_info path_info; path_info.path_established = path_state->path_established; - notify_path_state(skb, seq, + err = notify_path_state(skb, seq, path_state->connection, path_state->path, &path_info, NOTIFY_EXISTS | flags); @@ -6908,13 +6917,13 @@ static int get_initial_state(struct sk_buff *skb, struct netlink_callback *cb) } n -= state_change->n_paths; if (n < state_change->n_devices) { - notify_device_state_change(skb, seq, &state_change->devices[n], + err = notify_device_state_change(skb, seq, &state_change->devices[n], NOTIFY_EXISTS | flags); goto next; } n -= state_change->n_devices; if (n < state_change->n_devices * state_change->n_connections) { - notify_peer_device_state_change(skb, seq, &state_change->peer_devices[n], + err = notify_peer_device_state_change(skb, seq, &state_change->peer_devices[n], NOTIFY_EXISTS | flags); goto next; } @@ -6930,6 +6939,8 @@ next: cb->args[4] = 0; } out: + if (err) + return err; return skb->len; } diff --git a/drbd/drbd_state.c b/drbd/drbd_state.c index de53fa7a21f1..cec88b18852f 100644 --- a/drbd/drbd_state.c +++ b/drbd/drbd_state.c @@ -3062,7 +3062,7 @@ static union drbd_state state_change_word(struct drbd_state_change *state_change return state; } -void notify_resource_state_change(struct sk_buff *skb, +int notify_resource_state_change(struct sk_buff *skb, unsigned int seq, struct drbd_state_change *state_change, enum drbd_notification_type type) @@ -3079,10 +3079,10 @@ void notify_resource_state_change(struct sk_buff *skb, .res_susp_quorum = state_change_is_susp_quorum(state_change, NEW), }; - notify_resource_state(skb, seq, resource, &resource_info, NULL, type); + return notify_resource_state(skb, seq, resource, &resource_info, NULL, type); } -void notify_connection_state_change(struct sk_buff *skb, +int notify_connection_state_change(struct sk_buff *skb, unsigned int seq, struct drbd_connection_state_change *connection_state_change, enum drbd_notification_type type) @@ -3093,10 +3093,10 @@ void notify_connection_state_change(struct sk_buff *skb, .conn_role = connection_state_change->peer_role[NEW], }; - notify_connection_state(skb, seq, connection, &connection_info, type); + return notify_connection_state(skb, seq, connection, &connection_info, type); } -void notify_device_state_change(struct sk_buff *skb, +int notify_device_state_change(struct sk_buff *skb, unsigned int seq, struct drbd_device_state_change *device_state_change, enum drbd_notification_type type) @@ -3105,10 +3105,10 @@ void notify_device_state_change(struct sk_buff *skb, struct device_info device_info; device_state_change_to_info(&device_info, device_state_change); - notify_device_state(skb, seq, device, &device_info, type); + return notify_device_state(skb, seq, device, &device_info, type); } -void notify_peer_device_state_change(struct sk_buff *skb, +int notify_peer_device_state_change(struct sk_buff *skb, unsigned int seq, struct drbd_peer_device_state_change *state_change, enum drbd_notification_type type) @@ -3117,7 +3117,7 @@ void notify_peer_device_state_change(struct sk_buff *skb, struct peer_device_info peer_device_info; peer_device_state_change_to_info(&peer_device_info, state_change); - notify_peer_device_state(skb, seq, peer_device, &peer_device_info, type); + return notify_peer_device_state(skb, seq, peer_device, &peer_device_info, type); } static void notify_state_change(struct drbd_state_change *state_change) @@ -3125,7 +3125,7 @@ static void notify_state_change(struct drbd_state_change *state_change) struct drbd_resource_state_change *resource_state_change = &state_change->resource[0]; bool resource_state_has_changed; unsigned int n_device, n_connection, n_peer_device, n_peer_devices; - void (*last_func)(struct sk_buff *, unsigned int, void *, + int (*last_func)(struct sk_buff *, unsigned int, void *, enum drbd_notification_type) = NULL; void *last_arg = NULL; diff --git a/drbd/drbd_state_change.h b/drbd/drbd_state_change.h index e60ec1b8ee6e..df23977462c1 100644 --- a/drbd/drbd_state_change.h +++ b/drbd/drbd_state_change.h @@ -66,19 +66,19 @@ extern struct drbd_state_change *remember_state_change(struct drbd_resource *, g extern void copy_old_to_new_state_change(struct drbd_state_change *); extern void forget_state_change(struct drbd_state_change *); -extern void notify_resource_state_change(struct sk_buff *, +extern int notify_resource_state_change(struct sk_buff *, unsigned int, struct drbd_state_change *, enum drbd_notification_type type); -extern void notify_connection_state_change(struct sk_buff *, +extern int notify_connection_state_change(struct sk_buff *, unsigned int, struct drbd_connection_state_change *, enum drbd_notification_type type); -extern void notify_device_state_change(struct sk_buff *, +extern int notify_device_state_change(struct sk_buff *, unsigned int, struct drbd_device_state_change *, enum drbd_notification_type type); -extern void notify_peer_device_state_change(struct sk_buff *, +extern int notify_peer_device_state_change(struct sk_buff *, unsigned int, struct drbd_peer_device_state_change *, enum drbd_notification_type type); -- 2.26.2