392 lines
14 KiB
Diff
392 lines
14 KiB
Diff
|
From 2573d91be862ed65781c4a37dcaaa051a905a48a Mon Sep 17 00:00:00 2001
|
||
|
From: =?UTF-8?q?Christoph=20B=C3=B6hmwalder?=
|
||
|
<christoph.boehmwalder@linbit.com>
|
||
|
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
|
||
|
|