mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-27 16:28:58 -04:00
net/rds: hold the socket while an rds_mr references it
Each rds_mr stores a bare back pointer to the socket that created it
(mr->r_sock) but takes no reference on it. When the mr is destroyed it
references the rs. Hence, provisions must be made to avoid the rs
being destroyed before all mrs referencing it have been destroyed.
The MR itself is refcounted, and in-flight messages legitimately hold
MR krefs that can outlive the socket: rds_release() drops the rb-tree
references via rds_rdma_drop_keys(), but a send completion arriving
afterwards drops the final message reference from the CQ handler and
ends up in
rds_message_purge()
__rds_put_mr_final()
rds_destroy_mr() -> takes rs->rs_rdma_lock
dereferencing a socket that may already have been freed.
Oracle UEK fixed the same use-after-free ("rds: Add proper refcnt when
an RDS MR references an RDS Socket") after seeing crashes of the form:
PF: supervisor write access in kernel mode
_raw_spin_lock_irqsave+0x4a/0x6a
__rds_put_mr_final+0x2c/0xe0 [rds]
rds_message_purge+0x13c/0x150 [rds]
rds_message_put+0x39/0x54 [rds]
rds_ib_send_cqe_handler+0x147/0x3dd [rds_rdma]
To fix this, take a socket reference when an MR is created and drop it
when the final MR kref goes away. The reference cycle is broken by
rds_release(), which always runs rds_rdma_drop_keys() on close. So the
socket reference held by an MR never prevents release, it only delays
sk_free() until the last MR user is done.
The hold sits next to kref_init() at both allocation sites -
__rds_rdma_map() and the on-demand-paging path in
rds_cmsg_rdma_args() - so every MR owns exactly one socket reference
from the moment it becomes kref-managed. For that to work on the ODP
path, its get_mr() error handling is converted from a bare kfree() to
kref_put(..., __rds_put_mr_final), with r_trans_private cleared first
since it holds an ERR_PTR there; both sites then tear down through
the same path and a future error-path change cannot silently leak or
double-drop the reference.
Signed-off-by: Håkon Bugge <haakon.bugge@oracle.com>
[achender: port to net-next (sock_hold/sock_put in place of the UEK
rds_sock_addref/rds_sock_put helpers); also balance the reference on
the rds_cmsg_rdma_args() ODP path and unify its error path with
__rds_put_mr_final(); update commit message]
Signed-off-by: Allison Henderson <achender@kernel.org>
Link: https://patch.msgid.link/20260730041629.3512480-3-achender@kernel.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
committed by
Jakub Kicinski
parent
d100966325
commit
9079adef04
@@ -117,6 +117,7 @@ void __rds_put_mr_final(struct kref *kref)
|
||||
struct rds_mr *mr = container_of(kref, struct rds_mr, r_kref);
|
||||
|
||||
rds_destroy_mr(mr);
|
||||
sock_put(rds_rs_to_sk(mr->r_sock));
|
||||
kfree(mr);
|
||||
}
|
||||
|
||||
@@ -243,7 +244,11 @@ static int __rds_rdma_map(struct rds_sock *rs, struct rds_get_mr_args *args,
|
||||
kref_init(&mr->r_kref);
|
||||
RB_CLEAR_NODE(&mr->r_rb_node);
|
||||
mr->r_trans = rs->rs_transport;
|
||||
/* The MR can outlive its socket: a socket reference is held
|
||||
* until the final kref is dropped in __rds_put_mr_final().
|
||||
*/
|
||||
mr->r_sock = rs;
|
||||
sock_hold(rds_rs_to_sk(rs));
|
||||
|
||||
if (args->flags & RDS_RDMA_USE_ONCE)
|
||||
mr->r_use_once = 1;
|
||||
@@ -759,7 +764,12 @@ int rds_cmsg_rdma_args(struct rds_sock *rs, struct rds_message *rm,
|
||||
RB_CLEAR_NODE(&local_odp_mr->r_rb_node);
|
||||
kref_init(&local_odp_mr->r_kref);
|
||||
local_odp_mr->r_trans = rs->rs_transport;
|
||||
/* The MR can outlive its socket: a socket
|
||||
* reference is held until the final kref is
|
||||
* dropped in __rds_put_mr_final().
|
||||
*/
|
||||
local_odp_mr->r_sock = rs;
|
||||
sock_hold(rds_rs_to_sk(rs));
|
||||
local_odp_mr->r_trans_private =
|
||||
rs->rs_transport->get_mr(
|
||||
NULL, 0, rs, &local_odp_mr->r_key, NULL,
|
||||
@@ -768,7 +778,9 @@ int rds_cmsg_rdma_args(struct rds_sock *rs, struct rds_message *rm,
|
||||
ret = PTR_ERR(local_odp_mr->r_trans_private);
|
||||
rdsdebug("get_mr ret %d %p\"", ret,
|
||||
local_odp_mr->r_trans_private);
|
||||
kfree(local_odp_mr);
|
||||
local_odp_mr->r_trans_private = NULL;
|
||||
kref_put(&local_odp_mr->r_kref,
|
||||
__rds_put_mr_final);
|
||||
ret = -EOPNOTSUPP;
|
||||
goto out_pages;
|
||||
}
|
||||
|
||||
@@ -320,7 +320,10 @@ struct rds_mr {
|
||||
unsigned int r_invalidate:1;
|
||||
unsigned int r_write:1;
|
||||
|
||||
struct rds_sock *r_sock; /* back pointer to the socket that owns us */
|
||||
struct rds_sock *r_sock; /* socket that owns us; counted
|
||||
* reference, dropped by
|
||||
* __rds_put_mr_final()
|
||||
*/
|
||||
struct rds_transport *r_trans;
|
||||
void *r_trans_private;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user