summaryrefslogtreecommitdiff
path: root/fs/ceph
diff options
context:
space:
mode:
Diffstat (limited to 'fs/ceph')
-rw-r--r--fs/ceph/addr.c11
-rw-r--r--fs/ceph/caps.c74
-rw-r--r--fs/ceph/dir.c10
-rw-r--r--fs/ceph/export.c26
-rw-r--r--fs/ceph/mds_client.c73
-rw-r--r--fs/ceph/mds_client.h1
-rw-r--r--fs/ceph/mdsmap.c11
-rw-r--r--fs/ceph/super.h14
-rw-r--r--fs/ceph/xattr.c1
9 files changed, 191 insertions, 30 deletions
diff --git a/fs/ceph/addr.c b/fs/ceph/addr.c
index ecf33b66610c..e9f561bedf47 100644
--- a/fs/ceph/addr.c
+++ b/fs/ceph/addr.c
@@ -1426,6 +1426,16 @@ void ceph_shift_unused_folios_left(struct folio_batch *fbatch)
fbatch->nr = n;
}
+static void ceph_undo_wrbuffer_claim(struct inode *inode, struct folio *folio)
+{
+ struct ceph_snap_context *snapc = folio_detach_private(folio);
+
+ if (!snapc)
+ return;
+ ceph_put_wrbuffer_cap_refs(ceph_inode(inode), 1, snapc);
+ ceph_put_snap_context(snapc);
+}
+
static
int ceph_submit_write(struct address_space *mapping,
struct writeback_control *wbc,
@@ -1489,6 +1499,7 @@ new_request:
if (!page)
continue;
+ ceph_undo_wrbuffer_claim(inode, page_folio(page));
redirty_page_for_writepage(wbc, page);
unlock_page(page);
}
diff --git a/fs/ceph/caps.c b/fs/ceph/caps.c
index d7283fb54cec..bb5ef0eacd8f 100644
--- a/fs/ceph/caps.c
+++ b/fs/ceph/caps.c
@@ -979,6 +979,27 @@ int __ceph_caps_revoking_other(struct ceph_inode_info *ci,
return 0;
}
+/*
+ * Return true if any cap of this inode holds caps which the MDS has
+ * revoked, but which we have not released yet.
+ */
+static bool __ceph_is_any_revoking(const struct ceph_inode_info *ci)
+{
+ const struct rb_node *p;
+
+ lockdep_assert_held(&ci->i_ceph_lock);
+
+ for (p = rb_first(&ci->i_caps); p; p = rb_next(p)) {
+ const struct ceph_cap *cap =
+ rb_entry(p, struct ceph_cap, ci_node);
+
+ if (cap->implemented & ~cap->issued)
+ return true;
+ }
+
+ return false;
+}
+
int __ceph_caps_used(struct ceph_inode_info *ci)
{
int used = 0;
@@ -1421,6 +1442,9 @@ static void __prep_cap(struct cap_msg_args *arg, struct ceph_cap *cap,
cap->implemented &= cap->issued | used;
cap->mds_wanted = want;
+ if ((ci->i_ceph_flags & CEPH_I_FLUSH_FORCE) != 0 && !__ceph_is_any_revoking(ci))
+ clear_bit(CEPH_I_FLUSH_FORCE_BIT, &ci->i_ceph_flags);
+
arg->session = cap->session;
arg->ino = ceph_vino(inode).ino;
arg->cid = cap->cap_id;
@@ -2038,6 +2062,14 @@ void ceph_check_caps(struct ceph_inode_info *ci, int flags)
if (ci->i_ceph_flags & CEPH_I_FLUSH)
flags |= CHECK_CAPS_FLUSH;
+ /*
+ * A revoke whose response was deferred (see handle_cap_grant()) must
+ * still be acknowledged. Replay the forced flush here so that even a
+ * check triggered by writeback/invalidation completion sends a cap
+ * message to the MDS.
+ */
+ if (ci->i_ceph_flags & CEPH_I_FLUSH_FORCE)
+ flags |= CHECK_CAPS_FLUSH_FORCE;
retry:
/* Caps wanted by virtue of active open files. */
file_wanted = __ceph_caps_file_wanted(ci);
@@ -2589,9 +2621,14 @@ static void __kick_flushing_caps(struct ceph_mds_client *mdsc,
}
}
- list_for_each_entry(cf, &ci->i_cap_flush_list, i_list) {
- if (cf->tid < first_tid)
+ cf = list_first_entry(&ci->i_cap_flush_list, struct ceph_cap_flush, i_list);
+ while (&cf->i_list != &ci->i_cap_flush_list) {
+ struct ceph_cap_flush *next;
+
+ if (cf->tid < first_tid) {
+ cf = list_next_entry(cf, i_list);
continue;
+ }
cap = ci->i_auth_cap;
if (!(cap && cap->session == session)) {
@@ -2601,6 +2638,7 @@ static void __kick_flushing_caps(struct ceph_mds_client *mdsc,
}
first_tid = cf->tid + 1;
+ next = list_next_entry(cf, i_list);
if (!cf->is_capsnap) {
struct cap_msg_args arg;
@@ -2641,6 +2679,7 @@ static void __kick_flushing_caps(struct ceph_mds_client *mdsc,
}
spin_lock(&ci->i_ceph_lock);
+ cf = next;
}
}
@@ -3757,13 +3796,30 @@ static void handle_cap_grant(struct inode *inode,
BUG_ON(cap->issued & ~cap->implemented);
/* don't let check_caps skip sending a response to MDS for revoke msgs */
- if (!revoke_wait && le32_to_cpu(grant->op) == CEPH_CAP_OP_REVOKE) {
- cap->mds_wanted = 0;
- flags |= CHECK_CAPS_FLUSH_FORCE;
- if (cap == ci->i_auth_cap)
- check_caps = 1; /* check auth cap only */
- else
- check_caps = 2; /* check all caps */
+ if (le32_to_cpu(grant->op) == CEPH_CAP_OP_REVOKE) {
+ if (revoke_wait) {
+ /*
+ * We can't ack the revoke yet: the response is deferred
+ * until the writeback or cache invalidation queued above
+ * completes. Set the CEPH_I_FLUSH_FORCE flag to remember
+ * that a forced cap message is owed so that deferred
+ * completion (ceph_put_wrbuffer_cap_refs() or the
+ * invalidate worker, both of which call ceph_check_caps())
+ * actually sends one, even if by then the revoked caps look
+ * unused, the inode is retaining caps, or the MDS has
+ * re-granted them. Without this, the cap message is never
+ * sent and the MDS hangs ("isn't responding to
+ * mclientcaps(revoke)").
+ */
+ set_bit(CEPH_I_FLUSH_FORCE_BIT, &ci->i_ceph_flags);
+ } else {
+ cap->mds_wanted = 0;
+ flags |= CHECK_CAPS_FLUSH_FORCE;
+ if (cap == ci->i_auth_cap)
+ check_caps = 1; /* check auth cap only */
+ else
+ check_caps = 2; /* check all caps */
+ }
}
if (extra_info->inline_version > 0 &&
diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c
index ef9e92e362d3..40326517481c 100644
--- a/fs/ceph/dir.c
+++ b/fs/ceph/dir.c
@@ -1764,11 +1764,11 @@ static int __dir_lease_check(const struct dentry *dentry,
if (ret > 0) {
if (time_before(jiffies, di->time + lwc->dir_lease_ttl))
return STOP;
+ if (!lwc->expire_dir_lease)
+ return KEEP;
/* Move dentry to tail of dir lease list if we don't want
* to delete it. So dentries in the list are checked in a
* round robin manner */
- if (!lwc->expire_dir_lease)
- return TOUCH;
if (dentry->d_lockref.count > 0 ||
(di->flags & CEPH_DENTRY_REFERENCED))
return TOUCH;
@@ -1795,7 +1795,7 @@ int ceph_trim_dentries(struct ceph_mds_client *mdsc)
lwc.dir_lease = false;
lwc.nr_to_scan = CEPH_CAPS_PER_RELEASE * 2;
freed = __dentry_leases_walk(mdsc, &lwc);
- if (!lwc.nr_to_scan) /* more invalid leases */
+ if (freed > 0 && !lwc.nr_to_scan) /* more invalid leases */
return -EAGAIN;
if (lwc.nr_to_scan < CEPH_CAPS_PER_RELEASE)
@@ -1805,6 +1805,10 @@ int ceph_trim_dentries(struct ceph_mds_client *mdsc)
lwc.expire_dir_lease = freed < count;
lwc.dir_lease_ttl = mdsc->fsc->mount_options->caps_wanted_delay_max * HZ;
freed +=__dentry_leases_walk(mdsc, &lwc);
+ if (freed == 0 && count == 0)
+ /* no progress possible currently, retry futile */
+ return 0;
+
if (!lwc.nr_to_scan) /* more to check */
return -EAGAIN;
diff --git a/fs/ceph/export.c b/fs/ceph/export.c
index b2f2af104679..debb9634b9e3 100644
--- a/fs/ceph/export.c
+++ b/fs/ceph/export.c
@@ -442,6 +442,16 @@ static struct dentry *ceph_fh_to_parent(struct super_block *sb,
return dentry;
}
+static int ceph_export_copy_name(char *name, const char *src, u32 len)
+{
+ if (len > NAME_MAX)
+ return -ENAMETOOLONG;
+
+ memcpy(name, src, len);
+ name[len] = '\0';
+ return 0;
+}
+
static int __get_snap_name(struct dentry *parent, char *name,
struct dentry *child)
{
@@ -513,9 +523,8 @@ static int __get_snap_name(struct dentry *parent, char *name,
BUG_ON(!rde->inode.in);
if (ceph_snap(inode) ==
le64_to_cpu(rde->inode.in->snapid)) {
- memcpy(name, rde->name, rde->name_len);
- name[rde->name_len] = '\0';
- err = 0;
+ err = ceph_export_copy_name(name, rde->name,
+ rde->name_len);
goto out;
}
}
@@ -580,8 +589,8 @@ static int ceph_get_name(struct dentry *parent, char *name,
rinfo = &req->r_reply_info;
if (!IS_ENCRYPTED(dir)) {
- memcpy(name, rinfo->dname, rinfo->dname_len);
- name[rinfo->dname_len] = 0;
+ err = ceph_export_copy_name(name, rinfo->dname,
+ rinfo->dname_len);
} else {
struct fscrypt_str oname = FSTR_INIT(NULL, 0);
struct ceph_fname fname = { .dir = dir,
@@ -595,10 +604,9 @@ static int ceph_get_name(struct dentry *parent, char *name,
goto out;
err = ceph_fname_to_usr(&fname, NULL, &oname, NULL);
- if (!err) {
- memcpy(name, oname.name, oname.len);
- name[oname.len] = 0;
- }
+ if (!err)
+ err = ceph_export_copy_name(name, oname.name,
+ oname.len);
ceph_fname_free_buffer(dir, &oname);
}
out:
diff --git a/fs/ceph/mds_client.c b/fs/ceph/mds_client.c
index 3c692ad02c85..ec886ca9b526 100644
--- a/fs/ceph/mds_client.c
+++ b/fs/ceph/mds_client.c
@@ -615,10 +615,36 @@ bad:
#define DELEGATED_INO_AVAILABLE xa_mk_value(1)
+static int ceph_insert_deleg_ino(struct ceph_mds_session *s, u64 ino)
+{
+ struct ceph_client *cl = s->s_mdsc->fsc->client;
+ int err;
+
+ /*
+ * Cap how many delegated inodes a single session may hold. This is
+ * the only place that grows the count, so atomic_add_unless() bounds
+ * it at exactly CEPH_MAX_DELEG_INOS; s_num_deleg_inos can never exceed
+ * that.
+ */
+ if (!atomic_add_unless(&s->s_num_deleg_inos, 1, CEPH_MAX_DELEG_INOS)) {
+ pr_warn_ratelimited_client(cl,
+ "MDS session already holds %d delegated inodes\n",
+ CEPH_MAX_DELEG_INOS);
+ return -EOVERFLOW;
+ }
+
+ err = xa_insert(&s->s_delegated_inos, ino, DELEGATED_INO_AVAILABLE,
+ GFP_KERNEL);
+ if (err)
+ atomic_dec(&s->s_num_deleg_inos);
+ return err;
+}
+
static int ceph_parse_deleg_inos(void **p, void *end,
struct ceph_mds_session *s)
{
struct ceph_client *cl = s->s_mdsc->fsc->client;
+ u64 msg_deleg_inos = 0;
u32 sets;
ceph_decode_32_safe(p, end, sets, bad);
@@ -636,16 +662,34 @@ static int ceph_parse_deleg_inos(void **p, void *end,
start, len);
continue;
}
+
+ /*
+ * Bound the number of inodes one reply may delegate.
+ * ceph_insert_deleg_ino() separately caps the per-session
+ * population, so this only has to stop one reply from spinning
+ * the insert loop under an attacker-controlled len.
+ */
+ if (len > (u64)CEPH_MAX_DELEG_INOS ||
+ msg_deleg_inos > (u64)CEPH_MAX_DELEG_INOS - len) {
+ pr_warn_ratelimited_client(cl,
+ "MDS reply delegates too many inodes (have %llu, +%llu, max %d)\n",
+ msg_deleg_inos, len, CEPH_MAX_DELEG_INOS);
+ return -EIO;
+ }
+ msg_deleg_inos += len;
+
while (len--) {
- int err = xa_insert(&s->s_delegated_inos, start++,
- DELEGATED_INO_AVAILABLE,
- GFP_KERNEL);
+ int err = ceph_insert_deleg_ino(s, start++);
+
if (!err) {
doutc(cl, "added delegated inode 0x%llx\n", start - 1);
} else if (err == -EBUSY) {
pr_warn_client(cl,
"MDS delegated inode 0x%llx more than once.\n",
start - 1);
+ } else if (err == -EOVERFLOW) {
+ /* ceph_insert_deleg_ino() already warned. */
+ return -EIO;
} else {
return err;
}
@@ -663,16 +707,17 @@ u64 ceph_get_deleg_ino(struct ceph_mds_session *s)
xa_for_each(&s->s_delegated_inos, ino, val) {
val = xa_erase(&s->s_delegated_inos, ino);
- if (val == DELEGATED_INO_AVAILABLE)
+ if (val == DELEGATED_INO_AVAILABLE) {
+ atomic_dec(&s->s_num_deleg_inos);
return ino;
+ }
}
return 0;
}
int ceph_restore_deleg_ino(struct ceph_mds_session *s, u64 ino)
{
- return xa_insert(&s->s_delegated_inos, ino, DELEGATED_INO_AVAILABLE,
- GFP_KERNEL);
+ return ceph_insert_deleg_ino(s, ino);
}
#else /* BITS_PER_LONG == 64 */
/*
@@ -1059,6 +1104,7 @@ static struct ceph_mds_session *register_session(struct ceph_mds_client *mdsc,
INIT_LIST_HEAD(&s->s_waiting);
INIT_LIST_HEAD(&s->s_unsafe);
xa_init(&s->s_delegated_inos);
+ atomic_set(&s->s_num_deleg_inos, 0);
INIT_LIST_HEAD(&s->s_cap_releases);
INIT_WORK(&s->s_cap_release_work, ceph_cap_release_work);
@@ -4441,7 +4487,9 @@ static void handle_session(struct ceph_mds_session *session,
pr_err_client(cl, "No memory for path\n");
goto fail;
}
- ceph_decode_copy(&p, cap_auths[i].match.path, _len);
+ ceph_decode_copy_safe(&p, end,
+ cap_auths[i].match.path,
+ _len, bad);
/* Remove the tailing '/' */
while (_len && cap_auths[i].match.path[_len - 1] == '/') {
@@ -4458,7 +4506,9 @@ static void handle_session(struct ceph_mds_session *session,
pr_err_client(cl, "No memory for fs_name\n");
goto fail;
}
- ceph_decode_copy(&p, cap_auths[i].match.fs_name, _len);
+ ceph_decode_copy_safe(&p, end,
+ cap_auths[i].match.fs_name,
+ _len, bad);
}
ceph_decode_8_safe(&p, end, cap_auths[i].match.root_squash, bad);
@@ -5106,6 +5156,7 @@ static int send_mds_reconnect(struct ceph_mds_client *mdsc,
/* Serialized by s_mutex against concurrent ceph_get_deleg_ino(). */
xa_destroy(&session->s_delegated_inos);
+ atomic_set(&session->s_num_deleg_inos, 0);
if (session->s_state == CEPH_MDS_SESSION_CLOSED ||
session->s_state == CEPH_MDS_SESSION_REJECTED) {
pr_info_client(cl, "mds%d skipping reconnect, session %s\n",
@@ -5834,9 +5885,11 @@ static void check_new_map(struct ceph_mds_client *mdsc,
ceph_mdsmap_get_addr(newmap, i),
sizeof(struct ceph_entity_addr))) {
/* just close it */
+ ceph_get_mds_session(s);
mutex_unlock(&mdsc->mutex);
mutex_lock(&s->s_mutex);
mutex_lock(&mdsc->mutex);
+ ceph_put_mds_session(s);
ceph_con_close(&s->s_con);
mutex_unlock(&s->s_mutex);
s->s_state = CEPH_MDS_SESSION_RESTARTING;
@@ -5851,6 +5904,7 @@ static void check_new_map(struct ceph_mds_client *mdsc,
newstate >= CEPH_MDS_STATE_RECONNECT) {
int rc;
+ ceph_get_mds_session(s);
mutex_unlock(&mdsc->mutex);
clear_bit(i, targets);
rc = send_mds_reconnect(mdsc, s);
@@ -5859,6 +5913,7 @@ static void check_new_map(struct ceph_mds_client *mdsc,
"mds%d reconnect failed: %d\n",
i, rc);
mutex_lock(&mdsc->mutex);
+ ceph_put_mds_session(s);
}
/*
@@ -5871,9 +5926,11 @@ static void check_new_map(struct ceph_mds_client *mdsc,
pr_info_client(cl, "mds%d recovery completed\n",
s->s_mds);
kick_requests(mdsc, i);
+ ceph_get_mds_session(s);
mutex_unlock(&mdsc->mutex);
mutex_lock(&s->s_mutex);
mutex_lock(&mdsc->mutex);
+ ceph_put_mds_session(s);
ceph_kick_flushing_caps(mdsc, s);
mutex_unlock(&s->s_mutex);
wake_up_session_caps(s, RECONNECT);
diff --git a/fs/ceph/mds_client.h b/fs/ceph/mds_client.h
index 0ece4c9e3529..3c62e3c3530b 100644
--- a/fs/ceph/mds_client.h
+++ b/fs/ceph/mds_client.h
@@ -300,6 +300,7 @@ struct ceph_mds_session {
struct list_head s_waiting; /* waiting requests */
struct list_head s_unsafe; /* unsafe requests */
struct xarray s_delegated_inos;
+ atomic_t s_num_deleg_inos;
};
/*
diff --git a/fs/ceph/mdsmap.c b/fs/ceph/mdsmap.c
index 450a4dc9662e..53079ef34c3a 100644
--- a/fs/ceph/mdsmap.c
+++ b/fs/ceph/mdsmap.c
@@ -3,6 +3,7 @@
#include <linux/bug.h>
#include <linux/err.h>
+#include <linux/overflow.h>
#include <linux/random.h>
#include <linux/slab.h>
#include <linux/types.h>
@@ -126,6 +127,7 @@ struct ceph_mdsmap *ceph_mdsmap_decode(struct ceph_mds_client *mdsc, void **p,
u8 mdsmap_v;
u16 mdsmap_ev;
u32 target;
+ size_t export_targets_len;
m = kzalloc_obj(*m, GFP_NOFS);
if (!m)
@@ -224,8 +226,11 @@ struct ceph_mdsmap *ceph_mdsmap_decode(struct ceph_mds_client *mdsc, void **p,
*p += namelen;
if (info_v >= 2) {
ceph_decode_32_safe(p, end, num_export_targets, bad);
+ export_targets_len = size_mul(num_export_targets,
+ sizeof(u32));
+ ceph_decode_need(p, end, export_targets_len, bad);
pexport_targets = *p;
- *p += num_export_targets * sizeof(u32);
+ *p += export_targets_len;
} else {
num_export_targets = 0;
}
@@ -264,6 +269,10 @@ struct ceph_mdsmap *ceph_mdsmap_decode(struct ceph_mds_client *mdsc, void **p,
goto nomem;
for (j = 0; j < num_export_targets; j++) {
target = ceph_decode_32(&pexport_targets);
+ if (target >= CEPH_MAX_MDS) {
+ err = -EIO;
+ goto corrupt;
+ }
info->export_targets[j] = target;
}
} else {
diff --git a/fs/ceph/super.h b/fs/ceph/super.h
index 1d6aab060780..38f86294182c 100644
--- a/fs/ceph/super.h
+++ b/fs/ceph/super.h
@@ -641,6 +641,15 @@ static inline int ceph_ino_compare(struct inode *inode, void *data)
#define CEPH_MDS_INO_LOG_OFFSET (2 * CEPH_MAX_MDS)
#define CEPH_INO_SYSTEM_BASE ((6*CEPH_MAX_MDS) + (CEPH_MAX_MDS * CEPH_NUM_STRAY))
+/*
+ * Upper bound on the number of delegated inodes a single MDS session may
+ * hold. The MDS normally hands out a small preallocation window (the
+ * userspace mds_client_prealloc_inos option defaults to 1000) and refills
+ * it as the client consumes entries. This leaves generous headroom while
+ * bounding the CPU and memory a malformed delegation interval can consume.
+ */
+#define CEPH_MAX_DELEG_INOS 8192
+
static inline bool ceph_vino_is_reserved(const struct ceph_vino vino)
{
if (vino.ino >= CEPH_INO_SYSTEM_BASE ||
@@ -687,6 +696,10 @@ static inline struct inode *ceph_find_inode(struct super_block *sb,
#define CEPH_I_ASYNC_CREATE_BIT (12) /* async create in flight for this */
#define CEPH_I_SHUTDOWN_BIT (13) /* inode is no longer usable */
#define CEPH_I_ASYNC_CHECK_CAPS_BIT (14) /* check caps after async creating finishes */
+#define CEPH_I_FLUSH_FORCE_BIT (15) /* a revoke's response was deferred;
+ * force a cap message to the MDS once
+ * the deferred work completes
+ */
#define CEPH_I_DIR_ORDERED (1 << CEPH_I_DIR_ORDERED_BIT)
#define CEPH_I_FLUSH (1 << CEPH_I_FLUSH_BIT)
@@ -699,6 +712,7 @@ static inline struct inode *ceph_find_inode(struct super_block *sb,
#define CEPH_I_ODIRECT (1 << CEPH_I_ODIRECT_BIT)
#define CEPH_I_ASYNC_CREATE (1 << CEPH_I_ASYNC_CREATE_BIT)
#define CEPH_I_SHUTDOWN (1 << CEPH_I_SHUTDOWN_BIT)
+#define CEPH_I_FLUSH_FORCE (1 << CEPH_I_FLUSH_FORCE_BIT)
/*
* Masks of ceph inode work.
diff --git a/fs/ceph/xattr.c b/fs/ceph/xattr.c
index 860fc8e1867d..cc4ffbbcb719 100644
--- a/fs/ceph/xattr.c
+++ b/fs/ceph/xattr.c
@@ -848,6 +848,7 @@ start:
name = p;
p += len;
ceph_decode_32_safe(&p, end, len, bad);
+ ceph_decode_need(&p, end, len, bad);
val = p;
p += len;