Skip to content

Commit aaf67de

Browse files
lxbszidryomov
authored andcommitted
ceph: fix potential use-after-free bug when trimming caps
When trimming the caps and just after the 'session->s_cap_lock' is released in ceph_iterate_session_caps() the cap maybe removed by another thread, and when using the stale cap memory in the callbacks it will trigger use-after-free crash. We need to check the existence of the cap just after the 'ci->i_ceph_lock' being acquired. And do nothing if it's already removed. Cc: stable@vger.kernel.org Link: https://tracker.ceph.com/issues/43272 Signed-off-by: Xiubo Li <xiubli@redhat.com> Reviewed-by: Luís Henriques <lhenriques@suse.de> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
1 parent 7d41870 commit aaf67de

5 files changed

Lines changed: 62 additions & 35 deletions

File tree

fs/ceph/caps.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,7 @@ void ceph_reservation_status(struct ceph_fs_client *fsc,
431431
*
432432
* Called with i_ceph_lock held.
433433
*/
434-
static struct ceph_cap *__get_cap_for_mds(struct ceph_inode_info *ci, int mds)
434+
struct ceph_cap *__get_cap_for_mds(struct ceph_inode_info *ci, int mds)
435435
{
436436
struct ceph_cap *cap;
437437
struct rb_node *n = ci->i_caps.rb_node;

fs/ceph/debugfs.c

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -248,14 +248,20 @@ static int metrics_caps_show(struct seq_file *s, void *p)
248248
return 0;
249249
}
250250

251-
static int caps_show_cb(struct inode *inode, struct ceph_cap *cap, void *p)
251+
static int caps_show_cb(struct inode *inode, int mds, void *p)
252252
{
253+
struct ceph_inode_info *ci = ceph_inode(inode);
253254
struct seq_file *s = p;
254-
255-
seq_printf(s, "0x%-17llx%-3d%-17s%-17s\n", ceph_ino(inode),
256-
cap->session->s_mds,
257-
ceph_cap_string(cap->issued),
258-
ceph_cap_string(cap->implemented));
255+
struct ceph_cap *cap;
256+
257+
spin_lock(&ci->i_ceph_lock);
258+
cap = __get_cap_for_mds(ci, mds);
259+
if (cap)
260+
seq_printf(s, "0x%-17llx%-3d%-17s%-17s\n", ceph_ino(inode),
261+
cap->session->s_mds,
262+
ceph_cap_string(cap->issued),
263+
ceph_cap_string(cap->implemented));
264+
spin_unlock(&ci->i_ceph_lock);
259265
return 0;
260266
}
261267

fs/ceph/mds_client.c

Lines changed: 46 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1632,8 +1632,8 @@ static void cleanup_session_requests(struct ceph_mds_client *mdsc,
16321632
* Caller must hold session s_mutex.
16331633
*/
16341634
int ceph_iterate_session_caps(struct ceph_mds_session *session,
1635-
int (*cb)(struct inode *, struct ceph_cap *,
1636-
void *), void *arg)
1635+
int (*cb)(struct inode *, int mds, void *),
1636+
void *arg)
16371637
{
16381638
struct list_head *p;
16391639
struct ceph_cap *cap;
@@ -1645,13 +1645,16 @@ int ceph_iterate_session_caps(struct ceph_mds_session *session,
16451645
spin_lock(&session->s_cap_lock);
16461646
p = session->s_caps.next;
16471647
while (p != &session->s_caps) {
1648+
int mds;
1649+
16481650
cap = list_entry(p, struct ceph_cap, session_caps);
16491651
inode = igrab(&cap->ci->netfs.inode);
16501652
if (!inode) {
16511653
p = p->next;
16521654
continue;
16531655
}
16541656
session->s_cap_iterator = cap;
1657+
mds = cap->mds;
16551658
spin_unlock(&session->s_cap_lock);
16561659

16571660
if (last_inode) {
@@ -1663,7 +1666,7 @@ int ceph_iterate_session_caps(struct ceph_mds_session *session,
16631666
old_cap = NULL;
16641667
}
16651668

1666-
ret = cb(inode, cap, arg);
1669+
ret = cb(inode, mds, arg);
16671670
last_inode = inode;
16681671

16691672
spin_lock(&session->s_cap_lock);
@@ -1696,20 +1699,25 @@ int ceph_iterate_session_caps(struct ceph_mds_session *session,
16961699
return ret;
16971700
}
16981701

1699-
static int remove_session_caps_cb(struct inode *inode, struct ceph_cap *cap,
1700-
void *arg)
1702+
static int remove_session_caps_cb(struct inode *inode, int mds, void *arg)
17011703
{
17021704
struct ceph_inode_info *ci = ceph_inode(inode);
17031705
bool invalidate = false;
1704-
int iputs;
1706+
struct ceph_cap *cap;
1707+
int iputs = 0;
17051708

1706-
dout("removing cap %p, ci is %p, inode is %p\n",
1707-
cap, ci, &ci->netfs.inode);
17081709
spin_lock(&ci->i_ceph_lock);
1709-
iputs = ceph_purge_inode_cap(inode, cap, &invalidate);
1710+
cap = __get_cap_for_mds(ci, mds);
1711+
if (cap) {
1712+
dout(" removing cap %p, ci is %p, inode is %p\n",
1713+
cap, ci, &ci->netfs.inode);
1714+
1715+
iputs = ceph_purge_inode_cap(inode, cap, &invalidate);
1716+
}
17101717
spin_unlock(&ci->i_ceph_lock);
17111718

1712-
wake_up_all(&ci->i_cap_wq);
1719+
if (cap)
1720+
wake_up_all(&ci->i_cap_wq);
17131721
if (invalidate)
17141722
ceph_queue_invalidate(inode);
17151723
while (iputs--)
@@ -1780,8 +1788,7 @@ enum {
17801788
*
17811789
* caller must hold s_mutex.
17821790
*/
1783-
static int wake_up_session_cb(struct inode *inode, struct ceph_cap *cap,
1784-
void *arg)
1791+
static int wake_up_session_cb(struct inode *inode, int mds, void *arg)
17851792
{
17861793
struct ceph_inode_info *ci = ceph_inode(inode);
17871794
unsigned long ev = (unsigned long)arg;
@@ -1792,12 +1799,14 @@ static int wake_up_session_cb(struct inode *inode, struct ceph_cap *cap,
17921799
ci->i_requested_max_size = 0;
17931800
spin_unlock(&ci->i_ceph_lock);
17941801
} else if (ev == RENEWCAPS) {
1795-
if (cap->cap_gen < atomic_read(&cap->session->s_cap_gen)) {
1796-
/* mds did not re-issue stale cap */
1797-
spin_lock(&ci->i_ceph_lock);
1802+
struct ceph_cap *cap;
1803+
1804+
spin_lock(&ci->i_ceph_lock);
1805+
cap = __get_cap_for_mds(ci, mds);
1806+
/* mds did not re-issue stale cap */
1807+
if (cap && cap->cap_gen < atomic_read(&cap->session->s_cap_gen))
17981808
cap->issued = cap->implemented = CEPH_CAP_PIN;
1799-
spin_unlock(&ci->i_ceph_lock);
1800-
}
1809+
spin_unlock(&ci->i_ceph_lock);
18011810
} else if (ev == FORCE_RO) {
18021811
}
18031812
wake_up_all(&ci->i_cap_wq);
@@ -1959,16 +1968,22 @@ static bool drop_negative_children(struct dentry *dentry)
19591968
* Yes, this is a bit sloppy. Our only real goal here is to respond to
19601969
* memory pressure from the MDS, though, so it needn't be perfect.
19611970
*/
1962-
static int trim_caps_cb(struct inode *inode, struct ceph_cap *cap, void *arg)
1971+
static int trim_caps_cb(struct inode *inode, int mds, void *arg)
19631972
{
19641973
int *remaining = arg;
19651974
struct ceph_inode_info *ci = ceph_inode(inode);
19661975
int used, wanted, oissued, mine;
1976+
struct ceph_cap *cap;
19671977

19681978
if (*remaining <= 0)
19691979
return -1;
19701980

19711981
spin_lock(&ci->i_ceph_lock);
1982+
cap = __get_cap_for_mds(ci, mds);
1983+
if (!cap) {
1984+
spin_unlock(&ci->i_ceph_lock);
1985+
return 0;
1986+
}
19721987
mine = cap->issued | cap->implemented;
19731988
used = __ceph_caps_used(ci);
19741989
wanted = __ceph_caps_file_wanted(ci);
@@ -3911,26 +3926,22 @@ static struct dentry* d_find_primary(struct inode *inode)
39113926
/*
39123927
* Encode information about a cap for a reconnect with the MDS.
39133928
*/
3914-
static int reconnect_caps_cb(struct inode *inode, struct ceph_cap *cap,
3915-
void *arg)
3929+
static int reconnect_caps_cb(struct inode *inode, int mds, void *arg)
39163930
{
39173931
union {
39183932
struct ceph_mds_cap_reconnect v2;
39193933
struct ceph_mds_cap_reconnect_v1 v1;
39203934
} rec;
3921-
struct ceph_inode_info *ci = cap->ci;
3935+
struct ceph_inode_info *ci = ceph_inode(inode);
39223936
struct ceph_reconnect_state *recon_state = arg;
39233937
struct ceph_pagelist *pagelist = recon_state->pagelist;
39243938
struct dentry *dentry;
3939+
struct ceph_cap *cap;
39253940
char *path;
3926-
int pathlen = 0, err;
3941+
int pathlen = 0, err = 0;
39273942
u64 pathbase;
39283943
u64 snap_follows;
39293944

3930-
dout(" adding %p ino %llx.%llx cap %p %lld %s\n",
3931-
inode, ceph_vinop(inode), cap, cap->cap_id,
3932-
ceph_cap_string(cap->issued));
3933-
39343945
dentry = d_find_primary(inode);
39353946
if (dentry) {
39363947
/* set pathbase to parent dir when msg_version >= 2 */
@@ -3947,6 +3958,15 @@ static int reconnect_caps_cb(struct inode *inode, struct ceph_cap *cap,
39473958
}
39483959

39493960
spin_lock(&ci->i_ceph_lock);
3961+
cap = __get_cap_for_mds(ci, mds);
3962+
if (!cap) {
3963+
spin_unlock(&ci->i_ceph_lock);
3964+
goto out_err;
3965+
}
3966+
dout(" adding %p ino %llx.%llx cap %p %lld %s\n",
3967+
inode, ceph_vinop(inode), cap, cap->cap_id,
3968+
ceph_cap_string(cap->issued));
3969+
39503970
cap->seq = 0; /* reset cap seq */
39513971
cap->issue_seq = 0; /* and issue_seq */
39523972
cap->mseq = 0; /* and migrate_seq */

fs/ceph/mds_client.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -541,8 +541,7 @@ extern void ceph_flush_cap_releases(struct ceph_mds_client *mdsc,
541541
extern void ceph_queue_cap_reclaim_work(struct ceph_mds_client *mdsc);
542542
extern void ceph_reclaim_caps_nr(struct ceph_mds_client *mdsc, int nr);
543543
extern int ceph_iterate_session_caps(struct ceph_mds_session *session,
544-
int (*cb)(struct inode *,
545-
struct ceph_cap *, void *),
544+
int (*cb)(struct inode *, int mds, void *),
546545
void *arg);
547546
extern void ceph_mdsc_pre_umount(struct ceph_mds_client *mdsc);
548547

fs/ceph/super.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1192,6 +1192,8 @@ extern void ceph_kick_flushing_caps(struct ceph_mds_client *mdsc,
11921192
struct ceph_mds_session *session);
11931193
void ceph_kick_flushing_inode_caps(struct ceph_mds_session *session,
11941194
struct ceph_inode_info *ci);
1195+
extern struct ceph_cap *__get_cap_for_mds(struct ceph_inode_info *ci,
1196+
int mds);
11951197
extern struct ceph_cap *ceph_get_cap_for_mds(struct ceph_inode_info *ci,
11961198
int mds);
11971199
extern void ceph_take_cap_refs(struct ceph_inode_info *ci, int caps,

0 commit comments

Comments
 (0)