A bunch of assorted fixes with the majority being hardening against

malformed input and invalid data scenarios that don't happen in real
 deployments but can be utilized to trigger use-after-free and similar
 issues, some error path leak fixups and two patches from Max to avoid
 a potential hang in __ceph_get_caps() and unintended nesting of
 current->journal_info while handling replies from the MDS.  All marked
 for stable.
 -----BEGIN PGP SIGNATURE-----
 
 iQFHBAABCgAxFiEEydHwtzie9C7TfviiSn/eOAIR84sFAmpjx6wTHGlkcnlvbW92
 QGdtYWlsLmNvbQAKCRBKf944AhHzixDmB/4/J+dqhhDKg69t0ADnAPLgxe3AoXwi
 7GRd2Uk5/AU9o+1fexyfWt2u+3ZtAVGLVRZPF1ARFQQDGUoj99X06NlTyVAFRNMb
 p78Sun5iuiDJb72UOD3WzW9lmpjSeCVUbTuTadtmF4y34KIvZ7AYltjZBpycE2Yj
 lyiUr4CSkXMAa/wWDKg+8SAw2tBI61WdyJeyu8ESCUmm9Q2XLq1+2N/jhfifk0oC
 Dc7EHUZmg7YNbTr0DKyLXdAoZHE54zKC2qDrvfTPmpTsOhMI2v5LY6JtXzm8/l6c
 wAGgIj5hRePGDDPb1V5Mbz3rkY/VIGdKmkqEV4PZn+IHafZGp45lnS7V
 =q2wM
 -----END PGP SIGNATURE-----

Merge tag 'ceph-for-7.2-rc5' of https://github.com/ceph/ceph-client

Pull ceph fixes from Ilya Dryomov:
 "A bunch of assorted fixes with the majority being hardening against
  malformed input and invalid data scenarios that don't happen in real
  deployments but can be utilized to trigger use-after-free and similar
  issues, some error path leak fixups and two patches from Max to avoid
  a potential hang in __ceph_get_caps() and unintended nesting of
  current->journal_info while handling replies from the MDS.

  All marked for stable"

* tag 'ceph-for-7.2-rc5' of https://github.com/ceph/ceph-client:
  ceph: avoid fs reclaim while using current->journal_info
  ceph: add owner/capability checks for CEPH_IOC_SET_LAYOUT*
  ceph: fix hanging __ceph_get_caps() with stale mds_wanted
  rbd: Reset positive result codes to zero in object map update path
  libceph: bound pg_{temp,upmap,upmap_items} length to CEPH_PG_MAX_SIZE
  libceph: refresh auth->authorizer_buf{,_len} after authorizer update
  ceph: fix refcount leak in ceph_readdir()
  libceph: guard missing CRUSH type name lookup
  libceph: remove debugfs files before client teardown
  libceph: bound get_version reply decode to front len
  ceph: fix writeback_count leak in write_folio_nounlock()
  libceph: fix two unsafe bare decodes in decode_lockers()
  ceph: fix pre-auth out-of-bounds read on snaptrace in ceph_handle_caps()
  libceph: Reject monmaps advertising zero monitors
  libceph: reject zero bucket types in crush_decode
  libceph: Fix multiplication overflow in decode_new_up_state_weight()
This commit is contained in:
Linus Torvalds 2026-07-24 13:22:41 -07:00
commit dad0a87d79
13 changed files with 90 additions and 19 deletions

View File

@ -1950,9 +1950,14 @@ static int rbd_object_map_update_finish(struct rbd_obj_request *obj_req,
bool has_current_state;
void *p;
if (osd_req->r_result)
if (osd_req->r_result < 0)
return osd_req->r_result;
/*
* Writes aren't allowed to return a data payload.
*/
WARN_ON_ONCE(osd_req->r_result > 0);
/*
* Nothing to do for a snapshot object map.
*/

View File

@ -790,6 +790,9 @@ static int write_folio_nounlock(struct folio *folio,
ceph_wbc.truncate_size, true);
if (IS_ERR(req)) {
folio_redirty_for_writepage(wbc, folio);
if (atomic_long_dec_return(&fsc->writeback_count) <
CONGESTION_OFF_THRESH(fsc->mount_options->congestion_kb))
fsc->write_congested = false;
return PTR_ERR(req);
}
@ -809,6 +812,9 @@ static int write_folio_nounlock(struct folio *folio,
folio_redirty_for_writepage(wbc, folio);
folio_end_writeback(folio);
ceph_osdc_put_request(req);
if (atomic_long_dec_return(&fsc->writeback_count) <
CONGESTION_OFF_THRESH(fsc->mount_options->congestion_kb))
fsc->write_congested = false;
return PTR_ERR(bounce_page);
}
}
@ -847,6 +853,9 @@ static int write_folio_nounlock(struct folio *folio,
ceph_vinop(inode), folio);
folio_redirty_for_writepage(wbc, folio);
folio_end_writeback(folio);
if (atomic_long_dec_return(&fsc->writeback_count) <
CONGESTION_OFF_THRESH(fsc->mount_options->congestion_kb))
fsc->write_congested = false;
return err;
}
if (err == -EBLOCKLISTED)

View File

@ -3094,7 +3094,19 @@ int __ceph_get_caps(struct inode *inode, struct ceph_file_info *fi, int need,
ret = -ERESTARTSYS;
break;
}
wait_woken(&wait, TASK_INTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
/*
* If a cap update is lost after
* mds_wanted was raised, waiting
* forever will never make progress.
* Retry the renew path periodically
* so we can resend synchronously.
*/
if (!wait_woken(&wait, TASK_INTERRUPTIBLE,
CEPH_GET_CAPS_WAIT_TIMEOUT)) {
ret = -EUCLEAN;
break;
}
}
remove_wait_queue(&ci->i_cap_wq, &wait);
@ -3128,7 +3140,8 @@ int __ceph_get_caps(struct inode *inode, struct ceph_file_info *fi, int need,
continue;
}
if (ret == -EUCLEAN) {
/* session was killed, try renew caps */
/* session was killed or a waited cap
* request needs a retry */
ret = ceph_renew_caps(inode, flags);
if (ret == 0)
continue;
@ -4375,6 +4388,7 @@ void ceph_handle_caps(struct ceph_mds_session *session,
snaptrace = h + 1;
snaptrace_len = le32_to_cpu(h->snap_trace_len);
ceph_decode_need(&snaptrace, end, snaptrace_len, bad);
p = snaptrace + snaptrace_len;
if (msg_version >= 2) {

View File

@ -546,11 +546,16 @@ static int ceph_readdir(struct file *file, struct dir_context *ctx)
pr_warn_client(cl,
"%p %llx.%llx rde->offset 0x%llx ctx->pos 0x%llx\n",
inode, ceph_vinop(inode), rde->offset, ctx->pos);
ceph_mdsc_put_request(dfi->last_readdir);
dfi->last_readdir = NULL;
return -EIO;
}
if (WARN_ON_ONCE(!rde->inode.in))
if (WARN_ON_ONCE(!rde->inode.in)) {
ceph_mdsc_put_request(dfi->last_readdir);
dfi->last_readdir = NULL;
return -EIO;
}
ctx->pos = rde->offset;
doutc(cl, "%p %llx.%llx (%d/%d) -> %llx '%.*s' %p\n", inode,

View File

@ -314,7 +314,7 @@ static int ceph_init_file(struct inode *inode, struct file *file, int fmode)
}
/*
* try renew caps after session gets killed.
* Retry cap acquisition after a stale session or a lost cap update.
*/
int ceph_renew_caps(struct inode *inode, int fmode)
{
@ -322,14 +322,15 @@ int ceph_renew_caps(struct inode *inode, int fmode)
struct ceph_client *cl = mdsc->fsc->client;
struct ceph_inode_info *ci = ceph_inode(inode);
struct ceph_mds_request *req;
int err, flags, wanted;
int err, flags, wanted, issued;
spin_lock(&ci->i_ceph_lock);
__ceph_touch_fmode(ci, mdsc, fmode);
wanted = __ceph_caps_file_wanted(ci);
issued = __ceph_caps_issued(ci, NULL);
if (__ceph_is_any_real_caps(ci) &&
(!(wanted & CEPH_CAP_ANY_WR) || ci->i_auth_cap)) {
int issued = __ceph_caps_issued(ci, NULL);
(!(wanted & CEPH_CAP_ANY_WR) || ci->i_auth_cap) &&
(issued & wanted) == wanted) {
spin_unlock(&ci->i_ceph_lock);
doutc(cl, "%p %llx.%llx want %s issued %s updating mds_wanted\n",
inode, ceph_vinop(inode), ceph_cap_string(wanted),

View File

@ -72,6 +72,9 @@ static long ceph_ioctl_set_layout(struct file *file, void __user *arg)
struct ceph_ioctl_layout nl;
int err;
if (!inode_owner_or_capable(&nop_mnt_idmap, inode))
return -EACCES;
if (copy_from_user(&l, arg, sizeof(l)))
return -EFAULT;
@ -142,6 +145,9 @@ static long ceph_ioctl_set_layout_policy (struct file *file, void __user *arg)
int err;
struct ceph_mds_client *mdsc = ceph_sb_to_fs_client(inode->i_sb)->mdsc;
if (!inode_owner_or_capable(&nop_mnt_idmap, inode))
return -EACCES;
/* copy and validate */
if (copy_from_user(&l, arg, sizeof(l)))
return -EFAULT;

View File

@ -6,6 +6,7 @@
#include <linux/slab.h>
#include <linux/gfp.h>
#include <linux/sched.h>
#include <linux/sched/mm.h>
#include <linux/delay.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
@ -4015,6 +4016,7 @@ static void handle_reply(struct ceph_mds_session *session, struct ceph_msg *msg)
struct ceph_mds_reply_head *head = msg->front.iov_base;
struct ceph_mds_reply_info_parsed *rinfo; /* parsed reply info */
struct ceph_snap_realm *realm;
unsigned int nofs_flags;
u64 tid;
int err, result;
int mds = session->s_mds;
@ -4158,6 +4160,14 @@ static void handle_reply(struct ceph_mds_session *session, struct ceph_msg *msg)
/* insert trace into our cache */
mutex_lock(&req->r_fill_mutex);
/* disable fs reclaim while we are using current->journal_info
* for our own purposes, or else shrinkers of other
* filesystems might dereference this pointer as a different
* type
*/
nofs_flags = memalloc_nofs_save();
current->journal_info = req;
err = ceph_fill_trace(mdsc->fsc->sb, req);
if (err == 0) {
@ -4166,6 +4176,7 @@ static void handle_reply(struct ceph_mds_session *session, struct ceph_msg *msg)
err = ceph_readdir_prepopulate(req, req->r_session);
}
current->journal_info = NULL;
memalloc_nofs_restore(nofs_flags);
mutex_unlock(&req->r_fill_mutex);
up_read(&mdsc->snap_rwsem);

View File

@ -77,6 +77,7 @@ struct ceph_fs_client;
struct ceph_cap;
#define MDS_AUTH_UID_ANY -1
#define CEPH_GET_CAPS_WAIT_TIMEOUT (5 * HZ)
#define CEPH_CAP_FLUSH_WAIT_TIMEOUT_SEC 60
#define CEPH_CAP_FLUSH_MAX_DUMP_ENTRIES 5
#define CEPH_CAP_FLUSH_MAX_DUMP_ITERS 5

View File

@ -849,9 +849,16 @@ static int ceph_x_update_authorizer(
au = (struct ceph_x_authorizer *)auth->authorizer;
if (au->secret_id < th->secret_id) {
int ret;
dout("ceph_x_update_authorizer service %u secret %llu < %llu\n",
au->service, au->secret_id, th->secret_id);
return ceph_x_build_authorizer(ac, th, au);
ret = ceph_x_build_authorizer(ac, th, au);
if (ret)
return ret;
auth->authorizer_buf = au->buf->vec.iov_base;
auth->authorizer_buf_len = au->buf->vec.iov_len;
}
return 0;
}

View File

@ -762,13 +762,13 @@ void ceph_destroy_client(struct ceph_client *client)
atomic_set(&client->msgr.stopping, 1);
ceph_debugfs_client_cleanup(client);
/* unmount */
ceph_osdc_stop(&client->osdc);
ceph_monc_stop(&client->monc);
ceph_messenger_fini(&client->msgr);
ceph_debugfs_client_cleanup(client);
ceph_destroy_options(client->options);
kfree(client);

View File

@ -299,7 +299,7 @@ static int decode_lockers(void **p, void *end, u8 *type, char **tag,
if (ret)
return ret;
*num_lockers = ceph_decode_32(p);
ceph_decode_32_safe(p, end, *num_lockers, err_inval);
*lockers = kzalloc_objs(**lockers, *num_lockers, GFP_NOIO);
if (!*lockers)
return -ENOMEM;
@ -310,7 +310,8 @@ static int decode_lockers(void **p, void *end, u8 *type, char **tag,
goto err_free_lockers;
}
*type = ceph_decode_8(p);
ret = -EINVAL;
ceph_decode_8_safe(p, end, *type, err_free_lockers);
s = ceph_extract_encoded_string(p, end, NULL, GFP_NOIO);
if (IS_ERR(s)) {
ret = PTR_ERR(s);
@ -320,6 +321,9 @@ static int decode_lockers(void **p, void *end, u8 *type, char **tag,
*tag = s;
return 0;
err_inval:
return -EINVAL;
err_free_lockers:
ceph_free_lockers(*lockers, *num_lockers);
return ret;

View File

@ -114,7 +114,7 @@ static struct ceph_monmap *ceph_monmap_decode(void **p, void *end, bool msgr2)
dout("%s fsid %pU epoch %u num_mon %u\n", __func__, &fsid, epoch,
num_mon);
if (num_mon > CEPH_MAX_MON)
if (num_mon == 0 || num_mon > CEPH_MAX_MON)
goto e_inval;
monmap = kmalloc_flex(*monmap, mon_inst, num_mon, GFP_NOIO);
@ -821,7 +821,7 @@ static void handle_get_version_reply(struct ceph_mon_client *monc,
struct ceph_mon_generic_request *req;
u64 tid = le64_to_cpu(msg->hdr.tid);
void *p = msg->front.iov_base;
void *end = p + msg->front_alloc_len;
void *const end = p + msg->front.iov_len;
u64 handle;
dout("%s msg %p tid %llu\n", __func__, msg, tid);

View File

@ -518,6 +518,8 @@ static struct crush_map *crush_decode(void *pbyval, void *end)
ceph_decode_need(p, end, 4*sizeof(u32), bad);
b->id = ceph_decode_32(p);
b->type = ceph_decode_16(p);
if (b->type == 0)
goto bad;
b->alg = ceph_decode_8(p);
if (b->alg != alg) {
b->alg = 0;
@ -1436,7 +1438,7 @@ static struct ceph_pg_mapping *__decode_pg_temp(void **p, void *end,
ceph_decode_32_safe(p, end, len, e_inval);
if (len == 0 && incremental)
return NULL; /* new_pg_temp: [] to remove */
if ((size_t)len > (SIZE_MAX - sizeof(*pg)) / sizeof(u32))
if (len > CEPH_PG_MAX_SIZE)
return ERR_PTR(-EINVAL);
ceph_decode_need(p, end, len * sizeof(u32), e_inval);
@ -1617,7 +1619,7 @@ static struct ceph_pg_mapping *__decode_pg_upmap_items(void **p, void *end,
u32 len, i;
ceph_decode_32_safe(p, end, len, e_inval);
if ((size_t)len > (SIZE_MAX - sizeof(*pg)) / (2 * sizeof(u32)))
if (len > CEPH_PG_MAX_SIZE)
return ERR_PTR(-EINVAL);
ceph_decode_need(p, end, 2 * len * sizeof(u32), e_inval);
@ -1842,6 +1844,8 @@ static int decode_new_up_state_weight(void **p, void *end, u8 struct_v,
void *new_up_client;
void *new_state;
void *new_weight_end;
const u32 new_state_item_size =
sizeof(u32) + (struct_v >= 5 ? sizeof(u32) : sizeof(u8));
u32 len;
int ret;
int i;
@ -1862,7 +1866,8 @@ static int decode_new_up_state_weight(void **p, void *end, u8 struct_v,
new_state = *p;
ceph_decode_32_safe(p, end, len, e_inval);
len *= sizeof(u32) + (struct_v >= 5 ? sizeof(u32) : sizeof(u8));
if (check_mul_overflow(len, new_state_item_size, &len))
goto e_inval;
ceph_decode_need(p, end, len, e_inval);
*p += len;
@ -3055,8 +3060,11 @@ static int get_immediate_parent(struct crush_map *c, int id,
if (b->items[j] != id)
continue;
*parent_type_id = b->type;
type_cn = lookup_crush_name(&c->type_names, b->type);
if (WARN_ON_ONCE(!type_cn))
continue;
*parent_type_id = b->type;
parent_loc->cl_type_name = type_cn->cn_name;
parent_loc->cl_name = cn->cn_name;
return b->id;