All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v5 0/5] ceph: add support for snapshot names encryption
@ 2022-04-18 13:59 Luís Henriques
  2022-04-18 13:59 ` [PATCH v5 1/5] ceph: add support for encrypted snapshot names Luís Henriques
                   ` (5 more replies)
  0 siblings, 6 replies; 7+ messages in thread
From: Luís Henriques @ 2022-04-18 13:59 UTC (permalink / raw)
  To: Jeff Layton, Xiubo Li, Ilya Dryomov
  Cc: ceph-devel, linux-kernel, Luís Henriques

Hi!

And here's yet another revision.  The main difference from v4 is the
addition of a patch to prevent the creation of snapshots in directories
that are encrypted but that are not unlocked (i.e. no key available).

As before, the following ceph PRs are required:

  mds: add protection from clients without fscrypt support #45073
  mds: use the whole string as the snapshot long name #45192
  mds: support alternate names for snapshots #45224
  mds: limit the snapshot names to 240 characters #45312

Changes since v4:

- Explicitly added the encoding base for kstrtou64() as we do know that
  the inode numbers are in decimal.

- New patch to prevent the creation of snapshots in encrypted directories
  that do not have the key loaded.

Changes since v3:

- Fixed WARN_ON() in ceph_encode_encrypted_dname()

- Updated documentation and copyright notice for the base64
  encoding/decoding implementaiton which was taken from the fscrypt base.

Changes since v2:

- Use ceph_find_inode() instead of ceph_get_inode() for finding a snapshot
  parent in function parse_longname().  I've also added a fallback to
  ceph_get_inode() in case we fail to find the inode.  This may happen if,
  for example, the mount root doesn't include that inode.  The iput() was
  also complemented by a discard_new_inode() if the inode is in the I_NEW
  state. (patch 0002)

- Move the check for '_' snapshots further up in the ceph_fname_to_usr()
  and ceph_encode_encrypted_dname().  This fixes the case pointed out by
  Xiubo in v2. (patch 0002)

- Use NAME_MAX for tmp arrays (patch 0002)

- Added an extra patch for replacing the base64url encoding by a different
  encoding standard, the one used for IMAP mailboxes (which uses '+' and
  ',' instead of '-' and '_').  This should fix the issue with snapshot
  names starting with '_'. (patch 0003)

Changes since v1:

- Dropped the dentry->d_flags change in ceph_mkdir().  Thanks to Xiubo
  suggestion, patch 0001 now skips calling ceph_fscrypt_prepare_context()
  if we're handling a snapshot.

- Added error handling to ceph_get_snapdir() in patch 0001 (Jeff had
  already pointed that out but I forgot to include that change in previous
  revision).

- Rebased patch 0002 to the latest wip-fscrypt branch.

- Added some documentation regarding snapshots naming restrictions.


Luís Henriques (5):
  ceph: add support for encrypted snapshot names
  ceph: add support for handling encrypted snapshot names
  ceph: update documentation regarding snapshot naming limitations
  ceph: replace base64url by the encoding used for mailbox names
  ceph: prevent snapshots to be created in encrypted locked directories

 Documentation/filesystems/ceph.rst |  10 ++
 fs/ceph/crypto.c                   | 252 +++++++++++++++++++++++++----
 fs/ceph/crypto.h                   |  14 +-
 fs/ceph/dir.c                      |   7 +-
 fs/ceph/inode.c                    |  33 +++-
 5 files changed, 278 insertions(+), 38 deletions(-)


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH v5 1/5] ceph: add support for encrypted snapshot names
  2022-04-18 13:59 [PATCH v5 0/5] ceph: add support for snapshot names encryption Luís Henriques
@ 2022-04-18 13:59 ` Luís Henriques
  2022-04-18 13:59 ` [PATCH v5 2/5] ceph: add support for handling " Luís Henriques
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Luís Henriques @ 2022-04-18 13:59 UTC (permalink / raw)
  To: Jeff Layton, Xiubo Li, Ilya Dryomov
  Cc: ceph-devel, linux-kernel, Luís Henriques

Since filenames in encrypted directories are already encrypted and shown
as a base64-encoded string when the directory is locked, snapshot names
should show a similar behaviour.

Signed-off-by: Luís Henriques <lhenriques@suse.de>
---
 fs/ceph/inode.c | 31 +++++++++++++++++++++++++++----
 1 file changed, 27 insertions(+), 4 deletions(-)

diff --git a/fs/ceph/inode.c b/fs/ceph/inode.c
index fa0d3018d981..8e97efa2b1a7 100644
--- a/fs/ceph/inode.c
+++ b/fs/ceph/inode.c
@@ -91,9 +91,15 @@ struct inode *ceph_new_inode(struct inode *dir, struct dentry *dentry,
 	if (err < 0)
 		goto out_err;
 
-	err = ceph_fscrypt_prepare_context(dir, inode, as_ctx);
-	if (err)
-		goto out_err;
+	/*
+	 * We'll skip setting fscrypt context for snapshots, leaving that for
+	 * the handle_reply().
+	 */
+	if (ceph_snap(dir) != CEPH_SNAPDIR) {
+		err = ceph_fscrypt_prepare_context(dir, inode, as_ctx);
+		if (err)
+			goto out_err;
+	}
 
 	return inode;
 out_err:
@@ -157,6 +163,7 @@ struct inode *ceph_get_snapdir(struct inode *parent)
 	};
 	struct inode *inode = ceph_get_inode(parent->i_sb, vino, NULL);
 	struct ceph_inode_info *ci = ceph_inode(inode);
+	int ret = -ENOTDIR;
 
 	if (IS_ERR(inode))
 		return inode;
@@ -182,6 +189,22 @@ struct inode *ceph_get_snapdir(struct inode *parent)
 	ci->i_rbytes = 0;
 	ci->i_btime = ceph_inode(parent)->i_btime;
 
+	/* if encrypted, just borrow fscrypt_auth from parent */
+	if (IS_ENCRYPTED(parent)) {
+		struct ceph_inode_info *pci = ceph_inode(parent);
+
+		ci->fscrypt_auth = kmemdup(pci->fscrypt_auth,
+					   pci->fscrypt_auth_len,
+					   GFP_KERNEL);
+		if (ci->fscrypt_auth) {
+			inode->i_flags |= S_ENCRYPTED;
+			ci->fscrypt_auth_len = pci->fscrypt_auth_len;
+		} else {
+			dout("Failed to alloc snapdir fscrypt_auth\n");
+			ret = -ENOMEM;
+			goto err;
+		}
+	}
 	if (inode->i_state & I_NEW) {
 		inode->i_op = &ceph_snapdir_iops;
 		inode->i_fop = &ceph_snapdir_fops;
@@ -195,7 +218,7 @@ struct inode *ceph_get_snapdir(struct inode *parent)
 		discard_new_inode(inode);
 	else
 		iput(inode);
-	return ERR_PTR(-ENOTDIR);
+	return ERR_PTR(ret);
 }
 
 const struct inode_operations ceph_file_iops = {

^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v5 2/5] ceph: add support for handling encrypted snapshot names
  2022-04-18 13:59 [PATCH v5 0/5] ceph: add support for snapshot names encryption Luís Henriques
  2022-04-18 13:59 ` [PATCH v5 1/5] ceph: add support for encrypted snapshot names Luís Henriques
@ 2022-04-18 13:59 ` Luís Henriques
  2022-04-18 13:59 ` [PATCH v5 3/5] ceph: update documentation regarding snapshot naming limitations Luís Henriques
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Luís Henriques @ 2022-04-18 13:59 UTC (permalink / raw)
  To: Jeff Layton, Xiubo Li, Ilya Dryomov
  Cc: ceph-devel, linux-kernel, Luís Henriques

When creating a snapshot, the .snap directories for every subdirectory will
show the snapshot name in the "long format":

  # mkdir .snap/my-snap
  # ls my-dir/.snap/
  _my-snap_1099511627782

Encrypted snapshots will need to be able to handle these snapshot names by
encrypting/decrypting only the snapshot part of the string ('my-snap').

Also, since the MDS prevents snapshot names to be bigger than 240 characters
it is necessary to adapt CEPH_NOHASH_NAME_MAX to accommodate this extra
limitation.

Signed-off-by: Luís Henriques <lhenriques@suse.de>
---
 fs/ceph/crypto.c | 190 ++++++++++++++++++++++++++++++++++++++++-------
 fs/ceph/crypto.h |  11 ++-
 2 files changed, 170 insertions(+), 31 deletions(-)

diff --git a/fs/ceph/crypto.c b/fs/ceph/crypto.c
index e24e61c51118..e0bd7c3b7b23 100644
--- a/fs/ceph/crypto.c
+++ b/fs/ceph/crypto.c
@@ -129,16 +129,100 @@ void ceph_fscrypt_as_ctx_to_req(struct ceph_mds_request *req, struct ceph_acl_se
 	swap(req->r_fscrypt_auth, as->fscrypt_auth);
 }
 
-int ceph_encode_encrypted_dname(const struct inode *parent, struct qstr *d_name, char *buf)
+/*
+ * User-created snapshots can't start with '_'.  Snapshots that start with this
+ * character are special (hint: there aren't real snapshots) and use the
+ * following format:
+ *
+ *   _<SNAPSHOT-NAME>_<INODE-NUMBER>
+ *
+ * where:
+ *  - <SNAPSHOT-NAME> - the real snapshot name that may need to be decrypted,
+ *  - <INODE-NUMBER> - the inode number (in decimal) for the actual snapshot
+ *
+ * This function parses these snapshot names and returns the inode
+ * <INODE-NUMBER>.  'name_len' will also bet set with the <SNAPSHOT-NAME>
+ * length.
+ */
+static struct inode *parse_longname(const struct inode *parent, const char *name,
+				    int *name_len)
 {
+	struct inode *dir = NULL;
+	struct ceph_vino vino = { .snap = CEPH_NOSNAP };
+	char *inode_number;
+	char *name_end;
+	int orig_len = *name_len;
+	int ret = -EIO;
+
+	/* Skip initial '_' */
+	name++;
+	name_end = strrchr(name, '_');
+	if (!name_end) {
+		dout("Failed to parse long snapshot name: %s\n", name);
+		return ERR_PTR(-EIO);
+	}
+	*name_len = (name_end - name);
+	if (*name_len <= 0) {
+		pr_err("Failed to parse long snapshot name\n");
+		return ERR_PTR(-EIO);
+	}
+
+	/* Get the inode number */
+	inode_number = kmemdup_nul(name_end + 1,
+				   orig_len - *name_len - 2,
+				   GFP_KERNEL);
+	if (!inode_number)
+		return ERR_PTR(-ENOMEM);
+	ret = kstrtou64(inode_number, 10, &vino.ino);
+	if (ret) {
+		dout("Failed to parse inode number: %s\n", name);
+		dir = ERR_PTR(ret);
+		goto out;
+	}
+
+	/* And finally the inode */
+	dir = ceph_find_inode(parent->i_sb, vino);
+	if (!dir) {
+		/* This can happen if we're not mounting cephfs on the root */
+		dir = ceph_get_inode(parent->i_sb, vino, NULL);
+		if (!dir)
+			dir = ERR_PTR(-ENOENT);
+	}
+	if (IS_ERR(dir))
+		dout("Can't find inode %s (%s)\n", inode_number, name);
+
+out:
+	kfree(inode_number);
+	return dir;
+}
+
+int ceph_encode_encrypted_dname(struct inode *parent, struct qstr *d_name, char *buf)
+{
+	struct inode *dir = parent;
+	struct qstr iname;
 	u32 len;
+	int name_len;
 	int elen;
 	int ret;
-	u8 *cryptbuf;
+	u8 *cryptbuf = NULL;
+
+	iname.name = d_name->name;
+	name_len = d_name->len;
+
+	/* Handle the special case of snapshot names that start with '_' */
+	if ((ceph_snap(dir) == CEPH_SNAPDIR) && (name_len > 0) &&
+	    (iname.name[0] == '_')) {
+		dir = parse_longname(parent, iname.name, &name_len);
+		if (IS_ERR(dir))
+			return PTR_ERR(dir);
+		iname.name++; /* skip initial '_' */
+	}
+	iname.len = name_len;
 
-	if (!fscrypt_has_encryption_key(parent)) {
+	if (!fscrypt_has_encryption_key(dir)) {
 		memcpy(buf, d_name->name, d_name->len);
-		return d_name->len;
+		elen = d_name->len;
+		goto out;
 	}
 
 	/*
@@ -147,18 +231,22 @@ int ceph_encode_encrypted_dname(const struct inode *parent, struct qstr *d_name,
 	 *
 	 * See: fscrypt_setup_filename
 	 */
-	if (!fscrypt_fname_encrypted_size(parent, d_name->len, NAME_MAX, &len))
-		return -ENAMETOOLONG;
+	if (!fscrypt_fname_encrypted_size(dir, iname.len, NAME_MAX, &len)) {
+		elen = -ENAMETOOLONG;
+		goto out;
+	}
 
 	/* Allocate a buffer appropriate to hold the result */
 	cryptbuf = kmalloc(len > CEPH_NOHASH_NAME_MAX ? NAME_MAX : len, GFP_KERNEL);
-	if (!cryptbuf)
-		return -ENOMEM;
+	if (!cryptbuf) {
+		elen = -ENOMEM;
+		goto out;
+	}
 
-	ret = fscrypt_fname_encrypt(parent, d_name, cryptbuf, len);
+	ret = fscrypt_fname_encrypt(dir, &iname, cryptbuf, len);
 	if (ret) {
-		kfree(cryptbuf);
-		return ret;
+		elen = ret;
+		goto out;
 	}
 
 	/* hash the end if the name is long enough */
@@ -174,12 +262,30 @@ int ceph_encode_encrypted_dname(const struct inode *parent, struct qstr *d_name,
 
 	/* base64 encode the encrypted name */
 	elen = fscrypt_base64url_encode(cryptbuf, len, buf);
-	kfree(cryptbuf);
 	dout("base64-encoded ciphertext name = %.*s\n", elen, buf);
+
+	/* To understand the 240 limit, see CEPH_NOHASH_NAME_MAX comments */
+	WARN_ON(elen > 240);
+	if ((elen > 0) && (dir != parent)) {
+		char tmp_buf[NAME_MAX];
+
+		elen = snprintf(tmp_buf, sizeof(tmp_buf), "_%.*s_%ld",
+				elen, buf, dir->i_ino);
+		memcpy(buf, tmp_buf, elen);
+	}
+
+out:
+	kfree(cryptbuf);
+	if (dir != parent) {
+		if ((dir->i_state & I_NEW))
+			discard_new_inode(dir);
+		else
+			iput(dir);
+	}
 	return elen;
 }
 
-int ceph_encode_encrypted_fname(const struct inode *parent, struct dentry *dentry, char *buf)
+int ceph_encode_encrypted_fname(struct inode *parent, struct dentry *dentry, char *buf)
 {
 	WARN_ON_ONCE(!fscrypt_has_encryption_key(parent));
 
@@ -204,29 +310,42 @@ int ceph_encode_encrypted_fname(const struct inode *parent, struct dentry *dentr
 int ceph_fname_to_usr(const struct ceph_fname *fname, struct fscrypt_str *tname,
 		      struct fscrypt_str *oname, bool *is_nokey)
 {
-	int ret;
+	struct inode *dir = fname->dir;
 	struct fscrypt_str _tname = FSTR_INIT(NULL, 0);
 	struct fscrypt_str iname;
-
-	if (!IS_ENCRYPTED(fname->dir)) {
-		oname->name = fname->name;
-		oname->len = fname->name_len;
-		return 0;
-	}
+	char *name = fname->name;
+	int name_len = fname->name_len;
+	int ret;
 
 	/* Sanity check that the resulting name will fit in the buffer */
 	if (fname->name_len > NAME_MAX || fname->ctext_len > NAME_MAX)
 		return -EIO;
 
-	ret = __fscrypt_prepare_readdir(fname->dir);
+	/* Handle the special case of snapshot names that start with '_' */
+	if ((ceph_snap(dir) == CEPH_SNAPDIR) && (name_len > 0) &&
+	    (name[0] == '_')) {
+		dir = parse_longname(dir, name, &name_len);
+		if (IS_ERR(dir))
+			return PTR_ERR(dir);
+		name++; /* skip initial '_' */
+	}
+
+	if (!IS_ENCRYPTED(dir)) {
+		oname->name = fname->name;
+		oname->len = fname->name_len;
+		ret = 0;
+		goto out_inode;
+	}
+
+	ret = __fscrypt_prepare_readdir(dir);
 	if (ret)
-		return ret;
+		goto out_inode;
 
 	/*
 	 * Use the raw dentry name as sent by the MDS instead of
 	 * generating a nokey name via fscrypt.
 	 */
-	if (!fscrypt_has_encryption_key(fname->dir)) {
+	if (!fscrypt_has_encryption_key(dir)) {
 		if (fname->no_copy)
 			oname->name = fname->name;
 		else
@@ -234,7 +353,8 @@ int ceph_fname_to_usr(const struct ceph_fname *fname, struct fscrypt_str *tname,
 		oname->len = fname->name_len;
 		if (is_nokey)
 			*is_nokey = true;
-		return 0;
+		ret = 0;
+		goto out_inode;
 	}
 
 	if (fname->ctext_len == 0) {
@@ -243,11 +363,11 @@ int ceph_fname_to_usr(const struct ceph_fname *fname, struct fscrypt_str *tname,
 		if (!tname) {
 			ret = fscrypt_fname_alloc_buffer(NAME_MAX, &_tname);
 			if (ret)
-				return ret;
+				goto out_inode;
 			tname = &_tname;
 		}
 
-		declen = fscrypt_base64url_decode(fname->name, fname->name_len, tname->name);
+		declen = fscrypt_base64url_decode(name, name_len, tname->name);
 		if (declen <= 0) {
 			ret = -EIO;
 			goto out;
@@ -259,9 +379,25 @@ int ceph_fname_to_usr(const struct ceph_fname *fname, struct fscrypt_str *tname,
 		iname.len = fname->ctext_len;
 	}
 
-	ret = fscrypt_fname_disk_to_usr(fname->dir, 0, 0, &iname, oname);
+	ret = fscrypt_fname_disk_to_usr(dir, 0, 0, &iname, oname);
+	if (!ret && (dir != fname->dir)) {
+		char tmp_buf[FSCRYPT_BASE64URL_CHARS(NAME_MAX)];
+
+		name_len = snprintf(tmp_buf, sizeof(tmp_buf), "_%.*s_%ld",
+				    oname->len, oname->name, dir->i_ino);
+		memcpy(oname->name, tmp_buf, name_len);
+		oname->len = name_len;
+	}
+
 out:
 	fscrypt_fname_free_buffer(&_tname);
+out_inode:
+	if ((dir != fname->dir) && !IS_ERR(dir)) {
+		if ((dir->i_state & I_NEW))
+			discard_new_inode(dir);
+		else
+			iput(dir);
+	}
 	return ret;
 }
 
diff --git a/fs/ceph/crypto.h b/fs/ceph/crypto.h
index 0cf526f07567..0e10f934af5c 100644
--- a/fs/ceph/crypto.h
+++ b/fs/ceph/crypto.h
@@ -78,13 +78,16 @@ static inline u32 ceph_fscrypt_auth_len(struct ceph_fscrypt_auth *fa)
  * struct fscrypt_ceph_nokey_name {
  *	u8 bytes[157];
  *	u8 sha256[SHA256_DIGEST_SIZE];
- * }; // 189 bytes => 252 bytes base64-encoded, which is <= NAME_MAX (255)
+ * }; // 180 bytes => 240 bytes base64-encoded, which is <= NAME_MAX (255)
+ *
+ * (240 bytes is the maximum size allowed for snapshot names to take into
+ *  account the format: '_<SNAPSHOT-NAME>_<INODE-NUMBER>'.)
  *
  * Note that for long names that end up having their tail portion hashed, we
  * must also store the full encrypted name (in the dentry's alternate_name
  * field).
  */
-#define CEPH_NOHASH_NAME_MAX (189 - SHA256_DIGEST_SIZE)
+#define CEPH_NOHASH_NAME_MAX (180 - SHA256_DIGEST_SIZE)
 
 void ceph_fscrypt_set_ops(struct super_block *sb);
 
@@ -93,8 +96,8 @@ void ceph_fscrypt_free_dummy_policy(struct ceph_fs_client *fsc);
 int ceph_fscrypt_prepare_context(struct inode *dir, struct inode *inode,
 				 struct ceph_acl_sec_ctx *as);
 void ceph_fscrypt_as_ctx_to_req(struct ceph_mds_request *req, struct ceph_acl_sec_ctx *as);
-int ceph_encode_encrypted_dname(const struct inode *parent, struct qstr *d_name, char *buf);
-int ceph_encode_encrypted_fname(const struct inode *parent, struct dentry *dentry, char *buf);
+int ceph_encode_encrypted_dname(struct inode *parent, struct qstr *d_name, char *buf);
+int ceph_encode_encrypted_fname(struct inode *parent, struct dentry *dentry, char *buf);
 
 static inline int ceph_fname_alloc_buffer(struct inode *parent, struct fscrypt_str *fname)
 {

^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v5 3/5] ceph: update documentation regarding snapshot naming limitations
  2022-04-18 13:59 [PATCH v5 0/5] ceph: add support for snapshot names encryption Luís Henriques
  2022-04-18 13:59 ` [PATCH v5 1/5] ceph: add support for encrypted snapshot names Luís Henriques
  2022-04-18 13:59 ` [PATCH v5 2/5] ceph: add support for handling " Luís Henriques
@ 2022-04-18 13:59 ` Luís Henriques
  2022-04-18 13:59 ` [PATCH v5 4/5] ceph: replace base64url by the encoding used for mailbox names Luís Henriques
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Luís Henriques @ 2022-04-18 13:59 UTC (permalink / raw)
  To: Jeff Layton, Xiubo Li, Ilya Dryomov
  Cc: ceph-devel, linux-kernel, Luís Henriques

Signed-off-by: Luís Henriques <lhenriques@suse.de>
---
 Documentation/filesystems/ceph.rst | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/Documentation/filesystems/ceph.rst b/Documentation/filesystems/ceph.rst
index 4942e018db85..d487cabe792d 100644
--- a/Documentation/filesystems/ceph.rst
+++ b/Documentation/filesystems/ceph.rst
@@ -57,6 +57,16 @@ a snapshot on any subdirectory (and its nested contents) in the
 system.  Snapshot creation and deletion are as simple as 'mkdir
 .snap/foo' and 'rmdir .snap/foo'.
 
+Snapshot names have two limitations:
+
+* They can not start with an underscore ('_'), as these names are reserved
+  for internal usage by the MDS.
+* They can not exceed 240 characters in size.  This is because the MDS makes
+  use of long snapshot names internally, which follow the format:
+  `_<SNAPSHOT-NAME>_<INODE-NUMBER>`.  Since filenames in general can't have
+  more than 255 characters, and `<node-id>` takes 13 characters, the long
+  snapshot names can take as much as 255 - 1 - 1 - 13 = 240.
+
 Ceph also provides some recursive accounting on directories for nested
 files and bytes.  That is, a 'getfattr -d foo' on any directory in the
 system will reveal the total number of nested regular files and

^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v5 4/5] ceph: replace base64url by the encoding used for mailbox names
  2022-04-18 13:59 [PATCH v5 0/5] ceph: add support for snapshot names encryption Luís Henriques
                   ` (2 preceding siblings ...)
  2022-04-18 13:59 ` [PATCH v5 3/5] ceph: update documentation regarding snapshot naming limitations Luís Henriques
@ 2022-04-18 13:59 ` Luís Henriques
  2022-04-18 13:59 ` [PATCH v5 5/5] ceph: prevent snapshots to be created in encrypted locked directories Luís Henriques
  2022-04-18 14:31 ` [PATCH v5 0/5] ceph: add support for snapshot names encryption Jeff Layton
  5 siblings, 0 replies; 7+ messages in thread
From: Luís Henriques @ 2022-04-18 13:59 UTC (permalink / raw)
  To: Jeff Layton, Xiubo Li, Ilya Dryomov
  Cc: ceph-devel, linux-kernel, Luís Henriques

The base64url encoding includes the '_' character, which may cause problems
in snapshot names (if the name starts with '_').  Thus, use the base64
encoding defined for IMAP mailbox names (RFC 3501), which uses '+' and ','
instead of '-' and '_'.

Signed-off-by: Luís Henriques <lhenriques@suse.de>
---
 fs/ceph/crypto.c | 64 ++++++++++++++++++++++++++++++++++++++++++++++--
 fs/ceph/crypto.h |  3 +++
 fs/ceph/dir.c    |  2 +-
 fs/ceph/inode.c  |  2 +-
 4 files changed, 67 insertions(+), 4 deletions(-)

diff --git a/fs/ceph/crypto.c b/fs/ceph/crypto.c
index e0bd7c3b7b23..9bcdfa39aee5 100644
--- a/fs/ceph/crypto.c
+++ b/fs/ceph/crypto.c
@@ -1,4 +1,11 @@
 // SPDX-License-Identifier: GPL-2.0
+/*
+ * The base64 encode/decode code was copied from fscrypt:
+ * Copyright (C) 2015, Google, Inc.
+ * Copyright (C) 2015, Motorola Mobility
+ * Written by Uday Savagaonkar, 2014.
+ * Modified by Jaegeuk Kim, 2015.
+ */
 #include <linux/ceph/ceph_debug.h>
 #include <linux/xattr.h>
 #include <linux/fscrypt.h>
@@ -8,6 +15,59 @@
 #include "mds_client.h"
 #include "crypto.h"
 
+/*
+ * The base64url encoding used by fscrypt includes the '_' character, which may
+ * cause problems in snapshot names (which can not starts with '_').  Thus, we
+ * used the base64 encoding defined for IMAP mailbox names (RFC 3501) instead,
+ * which replaces '-' and '_' by '+' and ','.
+ */
+static const char base64_table[65] =
+        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,";
+
+int ceph_base64_encode(const u8 *src, int srclen, char *dst)
+{
+	u32 ac = 0;
+	int bits = 0;
+	int i;
+	char *cp = dst;
+
+	for (i = 0; i < srclen; i++) {
+		ac = (ac << 8) | src[i];
+		bits += 8;
+		do {
+			bits -= 6;
+			*cp++ = base64_table[(ac >> bits) & 0x3f];
+		} while (bits >= 6);
+	}
+	if (bits)
+		*cp++ = base64_table[(ac << (6 - bits)) & 0x3f];
+	return cp - dst;
+}
+
+int ceph_base64_decode(const char *src, int srclen, u8 *dst)
+{
+	u32 ac = 0;
+	int bits = 0;
+	int i;
+	u8 *bp = dst;
+
+	for (i = 0; i < srclen; i++) {
+		const char *p = strchr(base64_table, src[i]);
+
+		if (p == NULL || src[i] == 0)
+			return -1;
+		ac = (ac << 6) | (p - base64_table);
+		bits += 6;
+		if (bits >= 8) {
+			bits -= 8;
+			*bp++ = (u8)(ac >> bits);
+		}
+	}
+	if (ac & ((1 << bits) - 1))
+		return -1;
+	return bp - dst;
+}
+
 static int ceph_crypt_get_context(struct inode *inode, void *ctx, size_t len)
 {
 	struct ceph_inode_info *ci = ceph_inode(inode);
@@ -261,7 +321,7 @@ int ceph_encode_encrypted_dname(struct inode *parent, struct qstr *d_name, char
 	}
 
 	/* base64 encode the encrypted name */
-	elen = fscrypt_base64url_encode(cryptbuf, len, buf);
+	elen = ceph_base64_encode(cryptbuf, len, buf);
 	dout("base64-encoded ciphertext name = %.*s\n", elen, buf);
 
 	/* To understand the 240 limit, see CEPH_NOHASH_NAME_MAX comments */
@@ -367,7 +427,7 @@ int ceph_fname_to_usr(const struct ceph_fname *fname, struct fscrypt_str *tname,
 			tname = &_tname;
 		}
 
-		declen = fscrypt_base64url_decode(name, name_len, tname->name);
+		declen = ceph_base64_decode(name, name_len, tname->name);
 		if (declen <= 0) {
 			ret = -EIO;
 			goto out;
diff --git a/fs/ceph/crypto.h b/fs/ceph/crypto.h
index 0e10f934af5c..63fb230fcb41 100644
--- a/fs/ceph/crypto.h
+++ b/fs/ceph/crypto.h
@@ -89,6 +89,9 @@ static inline u32 ceph_fscrypt_auth_len(struct ceph_fscrypt_auth *fa)
  */
 #define CEPH_NOHASH_NAME_MAX (180 - SHA256_DIGEST_SIZE)
 
+int ceph_base64_encode(const u8 *src, int srclen, char *dst);
+int ceph_base64_decode(const char *src, int srclen, u8 *dst);
+
 void ceph_fscrypt_set_ops(struct super_block *sb);
 
 void ceph_fscrypt_free_dummy_policy(struct ceph_fs_client *fsc);
diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c
index 5ccf6453f02f..f48f1ff20927 100644
--- a/fs/ceph/dir.c
+++ b/fs/ceph/dir.c
@@ -966,7 +966,7 @@ static int prep_encrypted_symlink_target(struct ceph_mds_request *req, const cha
 		goto out;
 	}
 
-	len = fscrypt_base64url_encode(osd_link.name, osd_link.len, req->r_path2);
+	len = ceph_base64_encode(osd_link.name, osd_link.len, req->r_path2);
 	req->r_path2[len] = '\0';
 out:
 	fscrypt_fname_free_buffer(&osd_link);
diff --git a/fs/ceph/inode.c b/fs/ceph/inode.c
index 8e97efa2b1a7..1df2eab767ef 100644
--- a/fs/ceph/inode.c
+++ b/fs/ceph/inode.c
@@ -876,7 +876,7 @@ static int decode_encrypted_symlink(const char *encsym, int enclen, u8 **decsym)
 	if (!sym)
 		return -ENOMEM;
 
-	declen = fscrypt_base64url_decode(encsym, enclen, sym);
+	declen = ceph_base64_decode(encsym, enclen, sym);
 	if (declen < 0) {
 		pr_err("%s: can't decode symlink (%d). Content: %.*s\n",
 		       __func__, declen, enclen, encsym);

^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v5 5/5] ceph: prevent snapshots to be created in encrypted locked directories
  2022-04-18 13:59 [PATCH v5 0/5] ceph: add support for snapshot names encryption Luís Henriques
                   ` (3 preceding siblings ...)
  2022-04-18 13:59 ` [PATCH v5 4/5] ceph: replace base64url by the encoding used for mailbox names Luís Henriques
@ 2022-04-18 13:59 ` Luís Henriques
  2022-04-18 14:31 ` [PATCH v5 0/5] ceph: add support for snapshot names encryption Jeff Layton
  5 siblings, 0 replies; 7+ messages in thread
From: Luís Henriques @ 2022-04-18 13:59 UTC (permalink / raw)
  To: Jeff Layton, Xiubo Li, Ilya Dryomov
  Cc: ceph-devel, linux-kernel, Luís Henriques

With snapshot names encryption we can not allow snapshots to be created in
locked directories because the names wouldn't be encrypted.  This patch
forces the directory to be unlocked to allow a snapshot to be created.

Signed-off-by: Luís Henriques <lhenriques@suse.de>
---
 fs/ceph/dir.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c
index f48f1ff20927..44b7114ca267 100644
--- a/fs/ceph/dir.c
+++ b/fs/ceph/dir.c
@@ -1071,6 +1071,11 @@ static int ceph_mkdir(struct user_namespace *mnt_userns, struct inode *dir,
 		err = -EDQUOT;
 		goto out;
 	}
+	if ((op == CEPH_MDS_OP_MKSNAP) && IS_ENCRYPTED(dir) &&
+	    !fscrypt_has_encryption_key(dir)) {
+		err = -ENOKEY;
+		goto out;
+	}
 
 
 	req = ceph_mdsc_create_request(mdsc, op, USE_AUTH_MDS);

^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [PATCH v5 0/5] ceph: add support for snapshot names encryption
  2022-04-18 13:59 [PATCH v5 0/5] ceph: add support for snapshot names encryption Luís Henriques
                   ` (4 preceding siblings ...)
  2022-04-18 13:59 ` [PATCH v5 5/5] ceph: prevent snapshots to be created in encrypted locked directories Luís Henriques
@ 2022-04-18 14:31 ` Jeff Layton
  5 siblings, 0 replies; 7+ messages in thread
From: Jeff Layton @ 2022-04-18 14:31 UTC (permalink / raw)
  To: Luís Henriques, Xiubo Li, Ilya Dryomov; +Cc: ceph-devel, linux-kernel

On Mon, 2022-04-18 at 14:59 +0100, Luís Henriques wrote:
> Hi!
> 
> And here's yet another revision.  The main difference from v4 is the
> addition of a patch to prevent the creation of snapshots in directories
> that are encrypted but that are not unlocked (i.e. no key available).
> 
> As before, the following ceph PRs are required:
> 
>   mds: add protection from clients without fscrypt support #45073
>   mds: use the whole string as the snapshot long name #45192
>   mds: support alternate names for snapshots #45224
>   mds: limit the snapshot names to 240 characters #45312
> 
> Changes since v4:
> 
> - Explicitly added the encoding base for kstrtou64() as we do know that
>   the inode numbers are in decimal.
> 
> - New patch to prevent the creation of snapshots in encrypted directories
>   that do not have the key loaded.
> 
> Changes since v3:
> 
> - Fixed WARN_ON() in ceph_encode_encrypted_dname()
> 
> - Updated documentation and copyright notice for the base64
>   encoding/decoding implementaiton which was taken from the fscrypt base.
> 
> Changes since v2:
> 
> - Use ceph_find_inode() instead of ceph_get_inode() for finding a snapshot
>   parent in function parse_longname().  I've also added a fallback to
>   ceph_get_inode() in case we fail to find the inode.  This may happen if,
>   for example, the mount root doesn't include that inode.  The iput() was
>   also complemented by a discard_new_inode() if the inode is in the I_NEW
>   state. (patch 0002)
> 
> - Move the check for '_' snapshots further up in the ceph_fname_to_usr()
>   and ceph_encode_encrypted_dname().  This fixes the case pointed out by
>   Xiubo in v2. (patch 0002)
> 
> - Use NAME_MAX for tmp arrays (patch 0002)
> 
> - Added an extra patch for replacing the base64url encoding by a different
>   encoding standard, the one used for IMAP mailboxes (which uses '+' and
>   ',' instead of '-' and '_').  This should fix the issue with snapshot
>   names starting with '_'. (patch 0003)
> 
> Changes since v1:
> 
> - Dropped the dentry->d_flags change in ceph_mkdir().  Thanks to Xiubo
>   suggestion, patch 0001 now skips calling ceph_fscrypt_prepare_context()
>   if we're handling a snapshot.
> 
> - Added error handling to ceph_get_snapdir() in patch 0001 (Jeff had
>   already pointed that out but I forgot to include that change in previous
>   revision).
> 
> - Rebased patch 0002 to the latest wip-fscrypt branch.
> 
> - Added some documentation regarding snapshots naming restrictions.
> 
> 
> Luís Henriques (5):
>   ceph: add support for encrypted snapshot names
>   ceph: add support for handling encrypted snapshot names
>   ceph: update documentation regarding snapshot naming limitations
>   ceph: replace base64url by the encoding used for mailbox names
>   ceph: prevent snapshots to be created in encrypted locked directories
> 
>  Documentation/filesystems/ceph.rst |  10 ++
>  fs/ceph/crypto.c                   | 252 +++++++++++++++++++++++++----
>  fs/ceph/crypto.h                   |  14 +-
>  fs/ceph/dir.c                      |   7 +-
>  fs/ceph/inode.c                    |  33 +++-
>  5 files changed, 278 insertions(+), 38 deletions(-)
> 

Looks good. I'll plan to merge these into wip-fscrypt later today.

Thanks!
-- 
Jeff Layton <jlayton@kernel.org>

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2022-04-18 15:26 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-04-18 13:59 [PATCH v5 0/5] ceph: add support for snapshot names encryption Luís Henriques
2022-04-18 13:59 ` [PATCH v5 1/5] ceph: add support for encrypted snapshot names Luís Henriques
2022-04-18 13:59 ` [PATCH v5 2/5] ceph: add support for handling " Luís Henriques
2022-04-18 13:59 ` [PATCH v5 3/5] ceph: update documentation regarding snapshot naming limitations Luís Henriques
2022-04-18 13:59 ` [PATCH v5 4/5] ceph: replace base64url by the encoding used for mailbox names Luís Henriques
2022-04-18 13:59 ` [PATCH v5 5/5] ceph: prevent snapshots to be created in encrypted locked directories Luís Henriques
2022-04-18 14:31 ` [PATCH v5 0/5] ceph: add support for snapshot names encryption Jeff Layton

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.