From 3a12414a169a6199a5d187ed801aa9577c9f1192 Mon Sep 17 00:00:00 2001 From: Sebastien Buisson Date: Sat, 17 Jul 2021 19:01:25 -0500 Subject: [PATCH] LU-16374 enc: align Base64 encoding with RFC 4648 base64url Lustre encryption uses a Base64 encoding to encode no-key filenames (the filenames that are presented to userspace when a directory is listed without its encryption key). Make this Base64 encoding compliant with RFC 4648 base64url. And use '+' leading character to distringuish digested names. This is adapted from kernel commit ba47b515f594 fscrypt: align Base64 encoding with RFC 4648 base64url To maintain compatibility with older clients, a new llite parameter named 'filename_enc_use_old_base64' is introduced, set to 1 by default for Lustre 2.15. When 0, Lustre uses new-fashion base64 encoding. When set to 1, Lustre uses old-style base64 encoding. To set this parameter globally for all clients, do on the MGS: mgs# lctl set_param -P llite.*.filename_enc_use_old_base64={0,1} Lustre-change: https://review.whamcloud.com/49581 Lustre-commit: 583ee6911b6cac7f2867a37101cc069b4011b73f Signed-off-by: Eric Biggers Signed-off-by: Sebastien Buisson Change-Id: Iaa2256da7fb591d842b5bb7aa474b2ee6de9899d Reviewed-by: Andreas Dilger Reviewed-by: jsimmons Reviewed-on: https://review.whamcloud.com/c/fs/lustre-release/+/49945 Tested-by: jenkins Tested-by: Maloo Reviewed-by: James Simmons Reviewed-by: Oleg Drokin --- libcfs/include/libcfs/crypto/llcrypt.h | 2 + libcfs/libcfs/crypto/fname.c | 156 +++++++++++++++++++++++++++++---- lustre/include/lustre_disk.h | 1 + lustre/llite/crypto.c | 18 +++- lustre/llite/llite_lib.c | 3 + lustre/llite/lproc_llite.c | 49 +++++++++++ 6 files changed, 210 insertions(+), 19 deletions(-) diff --git a/libcfs/include/libcfs/crypto/llcrypt.h b/libcfs/include/libcfs/crypto/llcrypt.h index fae6a55..d05ff2a 100644 --- a/libcfs/include/libcfs/crypto/llcrypt.h +++ b/libcfs/include/libcfs/crypto/llcrypt.h @@ -53,6 +53,8 @@ struct llcrypt_name { /* Maximum value for the third parameter of llcrypt_operations.set_context(). */ #define LLCRYPT_SET_CONTEXT_MAX_SIZE 40 +#define LLCRYPT_DIGESTED_CHAR_OLD '_' +#define LLCRYPT_DIGESTED_CHAR '+' #ifdef CONFIG_LL_ENCRYPTION /* diff --git a/libcfs/libcfs/crypto/fname.c b/libcfs/libcfs/crypto/fname.c index 8b16c39..65b6b42 100644 --- a/libcfs/libcfs/crypto/fname.c +++ b/libcfs/libcfs/crypto/fname.c @@ -137,10 +137,19 @@ static int fname_decrypt(struct inode *inode, return 0; } +/* + * Old fashion base64 encoding, taken from Linux 5.4. + * + * This base64 encoding is specific to fscrypt and has been replaced since then + * with an RFC 4648 compliant base64-url encoding, see llcrypt_base64url_* + * below. + * The old fashion base64 encoding is kept for compatibility with older clients. + */ + static const char lookup_table[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,"; -#define BASE64_CHARS(nbytes) DIV_ROUND_UP((nbytes) * 4, 3) +#define LLCRYPT_BASE64_CHARS(nbytes) DIV_ROUND_UP((nbytes) * 4, 3) /** * base64_encode() - @@ -150,7 +159,7 @@ static const char lookup_table[65] = * * Return: length of the encoded string */ -static int base64_encode(const u8 *src, int len, char *dst) +static inline int llcrypt_base64_encode(const u8 *src, int len, char *dst) { int i, bits = 0, ac = 0; char *cp = dst; @@ -169,7 +178,7 @@ static int base64_encode(const u8 *src, int len, char *dst) return cp - dst; } -static int base64_decode(const char *src, int len, u8 *dst) +static inline int llcrypt_base64_decode(const char *src, int len, u8 *dst) { int i, bits = 0, ac = 0; const char *p; @@ -192,6 +201,98 @@ static int base64_decode(const char *src, int len, u8 *dst) return cp - dst; } +/* + * New fashion base64 encoding, taken from Linux 5.14. + * + * This base64 encoding is RFC 4648 compliant base64-url encoding. + */ + +static const char base64url_table[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + +#define LLCRYPT_BASE64URL_CHARS(nbytes) DIV_ROUND_UP((nbytes) * 4, 3) + +/** + * llcrypt_base64url_encode() - base64url-encode some binary data + * @src: the binary data to encode + * @srclen: the length of @src in bytes + * @dst: (output) the base64url-encoded string. Not NUL-terminated. + * + * Encodes data using base64url encoding, i.e. the "Base 64 Encoding with URL + * and Filename Safe Alphabet" specified by RFC 4648. '='-padding isn't used, + * as it's unneeded and not required by the RFC. base64url is used instead of + * base64 to avoid the '/' character, which isn't allowed in filenames. + * + * Return: the length of the resulting base64url-encoded string in bytes. + * This will be equal to LLCRYPT_BASE64URL_CHARS(srclen). + */ +static inline int llcrypt_base64url_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++ = base64url_table[(ac >> bits) & 0x3f]; + } while (bits >= 6); + } + if (bits) + *cp++ = base64url_table[(ac << (6 - bits)) & 0x3f]; + return cp - dst; +} + +/** + * llcrypt_base64url_decode() - base64url-decode a string + * @src: the string to decode. Doesn't need to be NUL-terminated. + * @srclen: the length of @src in bytes + * @dst: (output) the decoded binary data + * + * Decodes a string using base64url encoding, i.e. the "Base 64 Encoding with + * URL and Filename Safe Alphabet" specified by RFC 4648. '='-padding isn't + * accepted, nor are non-encoding characters such as whitespace. + * + * This implementation hasn't been optimized for performance. + * + * Return: the length of the resulting decoded binary data in bytes, + * or -1 if the string isn't a valid base64url string. + */ +static inline int llcrypt_base64url_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(base64url_table, src[i]); + + if (p == NULL || src[i] == 0) + return -1; + ac = (ac << 6) | (p - base64url_table); + bits += 6; + if (bits >= 8) { + bits -= 8; + *bp++ = (u8)(ac >> bits); + } + } + if (ac & ((1 << bits) - 1)) + return -1; + return bp - dst; +} + +static inline int base64_chars(struct lustre_sb_info *lsi, int nbytes) +{ + if (!(lsi->lsi_flags & LSI_FILENAME_ENC_B64_OLD_CLI)) + return LLCRYPT_BASE64URL_CHARS(nbytes); + else + return LLCRYPT_BASE64_CHARS(nbytes); +} + bool llcrypt_fname_encrypted_size(const struct inode *inode, u32 orig_len, u32 max_len, u32 *encrypted_len_ret) { @@ -225,9 +326,11 @@ int llcrypt_fname_alloc_buffer(const struct inode *inode, u32 max_encrypted_len, struct llcrypt_str *crypto_str) { + struct lustre_sb_info *lsi = s2lsi(inode->i_sb); const u32 max_encoded_len = - max_t(u32, BASE64_CHARS(LLCRYPT_FNAME_MAX_UNDIGESTED_SIZE), - 1 + BASE64_CHARS(sizeof(struct llcrypt_digested_name))); + max_t(u32, + base64_chars(lsi, LLCRYPT_FNAME_MAX_UNDIGESTED_SIZE), + 1 + base64_chars(lsi, sizeof(struct llcrypt_digested_name))); u32 max_presented_len; max_presented_len = max(max_encoded_len, max_encrypted_len); @@ -271,6 +374,8 @@ int llcrypt_fname_disk_to_usr(struct inode *inode, const struct llcrypt_str *iname, struct llcrypt_str *oname) { + int (*b64_encode)(const u8 *src, int srclen, char *dst); + struct lustre_sb_info *lsi = s2lsi(inode->i_sb); const struct qstr qname = LLTR_TO_QSTR(iname); struct llcrypt_digested_name digested_name; @@ -298,9 +403,13 @@ int llcrypt_fname_disk_to_usr(struct inode *inode, return 0; } + if (!(lsi->lsi_flags & LSI_FILENAME_ENC_B64_OLD_CLI)) + b64_encode = llcrypt_base64url_encode; + else + b64_encode = llcrypt_base64_encode; + if (iname->len <= LLCRYPT_FNAME_MAX_UNDIGESTED_SIZE) { - oname->len = base64_encode(iname->name, iname->len, - oname->name); + oname->len = b64_encode(iname->name, iname->len, oname->name); return 0; } if (hash) { @@ -313,9 +422,12 @@ int llcrypt_fname_disk_to_usr(struct inode *inode, memcpy(digested_name.digest, LLCRYPT_FNAME_DIGEST(iname->name, iname->len), LLCRYPT_FNAME_DIGEST_SIZE); - oname->name[0] = '_'; - oname->len = 1 + base64_encode((const u8 *)&digested_name, - sizeof(digested_name), oname->name + 1); + if (!(lsi->lsi_flags & LSI_FILENAME_ENC_B64_OLD_CLI)) + oname->name[0] = LLCRYPT_DIGESTED_CHAR; + else + oname->name[0] = LLCRYPT_DIGESTED_CHAR_OLD; + oname->len = 1 + b64_encode((const u8 *)&digested_name, + sizeof(digested_name), oname->name + 1); return 0; } EXPORT_SYMBOL(llcrypt_fname_disk_to_usr); @@ -347,6 +459,7 @@ EXPORT_SYMBOL(llcrypt_fname_disk_to_usr); int llcrypt_setup_filename(struct inode *dir, const struct qstr *iname, int lookup, struct llcrypt_name *fname) { + struct lustre_sb_info *lsi = s2lsi(dir->i_sb); int ret; int digested; @@ -399,14 +512,18 @@ int llcrypt_setup_filename(struct inode *dir, const struct qstr *iname, * We don't have the key and we are doing a lookup; decode the * user-supplied name */ - if (iname->name[0] == '_') { - if (iname->len != - 1 + BASE64_CHARS(sizeof(struct llcrypt_digested_name))) + if ((!(lsi->lsi_flags & LSI_FILENAME_ENC_B64_OLD_CLI) && + iname->name[0] == LLCRYPT_DIGESTED_CHAR) || + ((lsi->lsi_flags & LSI_FILENAME_ENC_B64_OLD_CLI) && + iname->name[0] == LLCRYPT_DIGESTED_CHAR_OLD)) { + if (iname->len != 1 + base64_chars(lsi, + sizeof(struct llcrypt_digested_name))) { return -ENOENT; + } digested = 1; } else { if (iname->len > - BASE64_CHARS(LLCRYPT_FNAME_MAX_UNDIGESTED_SIZE)) + base64_chars(lsi, LLCRYPT_FNAME_MAX_UNDIGESTED_SIZE)) return -ENOENT; digested = 0; } @@ -418,8 +535,15 @@ int llcrypt_setup_filename(struct inode *dir, const struct qstr *iname, if (fname->crypto_buf.name == NULL) return -ENOMEM; - ret = base64_decode(iname->name + digested, iname->len - digested, - fname->crypto_buf.name); + if (!(lsi->lsi_flags & LSI_FILENAME_ENC_B64_OLD_CLI)) + ret = llcrypt_base64url_decode(iname->name + digested, + iname->len - digested, + fname->crypto_buf.name); + else + ret = llcrypt_base64_decode(iname->name + digested, + iname->len - digested, + fname->crypto_buf.name); + if (ret < 0) { ret = -ENOENT; goto errout; diff --git a/lustre/include/lustre_disk.h b/lustre/include/lustre_disk.h index 7e7de02..23fe796 100644 --- a/lustre/include/lustre_disk.h +++ b/lustre/include/lustre_disk.h @@ -156,6 +156,7 @@ struct lustre_sb_info { #define LSI_BDI_INITIALIZED 0x00400000 #endif #define LSI_FILENAME_ENC 0x00800000 /* enable name encryption */ +#define LSI_FILENAME_ENC_B64_OLD_CLI 0x01000000 /* use old style base64 */ #define s2lsi(sb) ((struct lustre_sb_info *)((sb)->s_fs_info)) #define s2lsi_nocast(sb) ((sb)->s_fs_info) diff --git a/lustre/llite/crypto.c b/lustre/llite/crypto.c index d8e8173..a832d4d 100644 --- a/lustre/llite/crypto.c +++ b/lustre/llite/crypto.c @@ -252,8 +252,15 @@ int ll_setup_filename(struct inode *dir, const struct qstr *iname, int rc; if (fid && IS_ENCRYPTED(dir) && llcrypt_policy_has_filename_enc(dir) && - !llcrypt_has_encryption_key(dir) && iname->name[0] == '_') - digested = 1; + !llcrypt_has_encryption_key(dir)) { + struct lustre_sb_info *lsi = s2lsi(dir->i_sb); + + if ((!(lsi->lsi_flags & LSI_FILENAME_ENC_B64_OLD_CLI) && + iname->name[0] == LLCRYPT_DIGESTED_CHAR) || + ((lsi->lsi_flags & LSI_FILENAME_ENC_B64_OLD_CLI) && + iname->name[0] == LLCRYPT_DIGESTED_CHAR_OLD)) + digested = 1; + } dname.name = iname->name + digested; dname.len = iname->len - digested; @@ -394,6 +401,8 @@ int ll_fname_disk_to_usr(struct inode *inode, if (lltr.len > LL_CRYPTO_BLOCK_SIZE * 2 && !llcrypt_has_encryption_key(inode) && llcrypt_policy_has_filename_enc(inode)) { + struct lustre_sb_info *lsi = s2lsi(inode->i_sb); + digested = 1; /* Without the key for long names, set the dentry name * to the representing struct ll_digest_filename. It @@ -410,7 +419,10 @@ int ll_fname_disk_to_usr(struct inode *inode, lltr.name = (char *)&digest; lltr.len = sizeof(digest); - oname->name[0] = '_'; + if (!(lsi->lsi_flags & LSI_FILENAME_ENC_B64_OLD_CLI)) + oname->name[0] = LLCRYPT_DIGESTED_CHAR; + else + oname->name[0] = LLCRYPT_DIGESTED_CHAR_OLD; oname->name = oname->name + 1; oname->len--; } diff --git a/lustre/llite/llite_lib.c b/lustre/llite/llite_lib.c index 02e657c..6c6bee6 100644 --- a/lustre/llite/llite_lib.c +++ b/lustre/llite/llite_lib.c @@ -510,6 +510,7 @@ static int client_common_fill_super(struct super_block *sb, char *md, char *dt) sbi->ll_fsname, sbi->ll_md_exp->exp_obd->obd_name); lsi->lsi_flags &= ~LSI_FILENAME_ENC; + lsi->lsi_flags &= ~LSI_FILENAME_ENC_B64_OLD_CLI; ll_sbi_set_name_encrypt(sbi, false); } @@ -1322,6 +1323,8 @@ int ll_fill_super(struct super_block *sb) else /* filename encryption is disabled by default */ lsi->lsi_flags &= ~LSI_FILENAME_ENC; + /* Lustre 2.15 uses old-style base64 encoding by default */ + lsi->lsi_flags |= LSI_FILENAME_ENC_B64_OLD_CLI; #endif /* kernel >= 2.6.38 store dentry operations in sb->s_d_op. */ diff --git a/lustre/llite/lproc_llite.c b/lustre/llite/lproc_llite.c index 2499f45..9e7caf7 100644 --- a/lustre/llite/lproc_llite.c +++ b/lustre/llite/lproc_llite.c @@ -1636,6 +1636,53 @@ static ssize_t ll_filename_enc_seq_write(struct file *file, } LDEBUGFS_SEQ_FOPS(ll_filename_enc); + +static int ll_old_b64_enc_seq_show(struct seq_file *m, void *v) +{ + struct super_block *sb = m->private; + struct lustre_sb_info *lsi = s2lsi(sb); + + seq_printf(m, "%u\n", + lsi->lsi_flags & LSI_FILENAME_ENC_B64_OLD_CLI ? 1 : 0); + return 0; +} + +static ssize_t ll_old_b64_enc_seq_write(struct file *file, + const char __user *buffer, + size_t count, loff_t *off) +{ + struct seq_file *m = file->private_data; + struct super_block *sb = m->private; + struct lustre_sb_info *lsi = s2lsi(sb); + struct ll_sb_info *sbi = ll_s2sbi(sb); + bool val; + int rc; + + rc = kstrtobool_from_user(buffer, count, &val); + if (rc) + return rc; + + if (val) { + if (!ll_sbi_has_name_encrypt(sbi)) { + /* server does not support name encryption, + * so force it to NULL on client + */ + CDEBUG(D_SEC, + "%s: server does not support name encryption\n", + sbi->ll_fsname); + lsi->lsi_flags &= ~LSI_FILENAME_ENC_B64_OLD_CLI; + return -EOPNOTSUPP; + } + + lsi->lsi_flags |= LSI_FILENAME_ENC_B64_OLD_CLI; + } else { + lsi->lsi_flags &= ~LSI_FILENAME_ENC_B64_OLD_CLI; + } + + return count; +} + +LDEBUGFS_SEQ_FOPS(ll_old_b64_enc); #endif /* CONFIG_LL_ENCRYPTION */ static int ll_pcc_seq_show(struct seq_file *m, void *v) @@ -1695,6 +1742,8 @@ struct ldebugfs_vars lprocfs_llite_obd_vars[] = { #ifdef CONFIG_LL_ENCRYPTION { .name = "enable_filename_encryption", .fops = &ll_filename_enc_fops, }, + { .name = "filename_enc_use_old_base64", + .fops = &ll_old_b64_enc_fops, }, #endif { NULL } }; -- 1.8.3.1