Skip to content

Commit

Permalink
Merge tag 'fscrypt-for-linus' of git://git.kernel.org/pub/scm/fs/fscr…
Browse files Browse the repository at this point in the history
…ypt/fscrypt

Pull fscrypt updates from Eric Biggers:
 "Some small fixes and cleanups for fs/crypto/:

   - Fix ->getattr() for ext4, f2fs, and ubifs to report the correct
     st_size for encrypted symlinks

   - Use base64url instead of a custom Base64 variant

   - Document struct fscrypt_operations"

* tag 'fscrypt-for-linus' of git://git.kernel.org/pub/scm/fs/fscrypt/fscrypt:
  fscrypt: document struct fscrypt_operations
  fscrypt: align Base64 encoding with RFC 4648 base64url
  fscrypt: remove mention of symlink st_size quirk from documentation
  ubifs: report correct st_size for encrypted symlinks
  f2fs: report correct st_size for encrypted symlinks
  ext4: report correct st_size for encrypted symlinks
  fscrypt: add fscrypt_symlink_getattr() for computing st_size
  • Loading branch information
torvalds committed Aug 31, 2021
2 parents 87045e6 + 38ef66b commit cd35820
Show file tree
Hide file tree
Showing 7 changed files with 260 additions and 58 deletions.
15 changes: 5 additions & 10 deletions Documentation/filesystems/fscrypt.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1063,11 +1063,6 @@ astute users may notice some differences in behavior:

- DAX (Direct Access) is not supported on encrypted files.

- The st_size of an encrypted symlink will not necessarily give the
length of the symlink target as required by POSIX. It will actually
give the length of the ciphertext, which will be slightly longer
than the plaintext due to NUL-padding and an extra 2-byte overhead.

- The maximum length of an encrypted symlink is 2 bytes shorter than
the maximum length of an unencrypted symlink. For example, on an
EXT4 filesystem with a 4K block size, unencrypted symlinks can be up
Expand Down Expand Up @@ -1235,12 +1230,12 @@ the user-supplied name to get the ciphertext.

Lookups without the key are more complicated. The raw ciphertext may
contain the ``\0`` and ``/`` characters, which are illegal in
filenames. Therefore, readdir() must base64-encode the ciphertext for
presentation. For most filenames, this works fine; on ->lookup(), the
filesystem just base64-decodes the user-supplied name to get back to
the raw ciphertext.
filenames. Therefore, readdir() must base64url-encode the ciphertext
for presentation. For most filenames, this works fine; on ->lookup(),
the filesystem just base64url-decodes the user-supplied name to get
back to the raw ciphertext.

However, for very long filenames, base64 encoding would cause the
However, for very long filenames, base64url encoding would cause the
filename length to exceed NAME_MAX. To prevent this, readdir()
actually presents long filenames in an abbreviated form which encodes
a strong "hash" of the ciphertext filename, along with the optional
Expand Down
106 changes: 65 additions & 41 deletions fs/crypto/fname.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
* it to find the directory entry again if requested. Naively, that would just
* mean using the ciphertext filenames. However, since the ciphertext filenames
* can contain illegal characters ('\0' and '/'), they must be encoded in some
* way. We use base64. But that can cause names to exceed NAME_MAX (255
* way. We use base64url. But that can cause names to exceed NAME_MAX (255
* bytes), so we also need to use a strong hash to abbreviate long names.
*
* The filesystem may also need another kind of hash, the "dirhash", to quickly
Expand All @@ -38,7 +38,7 @@
* casefolded directories use this type of dirhash. At least in these cases,
* each no-key name must include the name's dirhash too.
*
* To meet all these requirements, we base64-encode the following
* To meet all these requirements, we base64url-encode the following
* variable-length structure. It contains the dirhash, or 0's if the filesystem
* didn't provide one; up to 149 bytes of the ciphertext name; and for
* ciphertexts longer than 149 bytes, also the SHA-256 of the remaining bytes.
Expand All @@ -52,15 +52,19 @@ struct fscrypt_nokey_name {
u32 dirhash[2];
u8 bytes[149];
u8 sha256[SHA256_DIGEST_SIZE];
}; /* 189 bytes => 252 bytes base64-encoded, which is <= NAME_MAX (255) */
}; /* 189 bytes => 252 bytes base64url-encoded, which is <= NAME_MAX (255) */

/*
* Decoded size of max-size nokey name, i.e. a name that was abbreviated using
* Decoded size of max-size no-key name, i.e. a name that was abbreviated using
* the strong hash and thus includes the 'sha256' field. This isn't simply
* sizeof(struct fscrypt_nokey_name), as the padding at the end isn't included.
*/
#define FSCRYPT_NOKEY_NAME_MAX offsetofend(struct fscrypt_nokey_name, sha256)

/* Encoded size of max-size no-key name */
#define FSCRYPT_NOKEY_NAME_MAX_ENCODED \
FSCRYPT_BASE64URL_CHARS(FSCRYPT_NOKEY_NAME_MAX)

static inline bool fscrypt_is_dot_dotdot(const struct qstr *str)
{
if (str->len == 1 && str->name[0] == '.')
Expand Down Expand Up @@ -175,62 +179,82 @@ static int fname_decrypt(const struct inode *inode,
return 0;
}

static const char lookup_table[65] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,";
static const char base64url_table[65] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";

#define BASE64_CHARS(nbytes) DIV_ROUND_UP((nbytes) * 4, 3)
#define FSCRYPT_BASE64URL_CHARS(nbytes) DIV_ROUND_UP((nbytes) * 4, 3)

/**
* base64_encode() - base64-encode some bytes
* @src: the bytes to encode
* @len: number of bytes to encode
* @dst: (output) the base64-encoded string. Not NUL-terminated.
* fscrypt_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 the input string using characters from the set [A-Za-z0-9+,].
* The encoded string is roughly 4/3 times the size of the input string.
* 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: length of the encoded string
* Return: the length of the resulting base64url-encoded string in bytes.
* This will be equal to FSCRYPT_BASE64URL_CHARS(srclen).
*/
static int base64_encode(const u8 *src, int len, char *dst)
static int fscrypt_base64url_encode(const u8 *src, int srclen, char *dst)
{
int i, bits = 0, ac = 0;
u32 ac = 0;
int bits = 0;
int i;
char *cp = dst;

for (i = 0; i < len; i++) {
ac += src[i] << bits;
for (i = 0; i < srclen; i++) {
ac = (ac << 8) | src[i];
bits += 8;
do {
*cp++ = lookup_table[ac & 0x3f];
ac >>= 6;
bits -= 6;
*cp++ = base64url_table[(ac >> bits) & 0x3f];
} while (bits >= 6);
}
if (bits)
*cp++ = lookup_table[ac & 0x3f];
*cp++ = base64url_table[(ac << (6 - bits)) & 0x3f];
return cp - dst;
}

static int base64_decode(const char *src, int len, u8 *dst)
/**
* fscrypt_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 int fscrypt_base64url_decode(const char *src, int srclen, u8 *dst)
{
int i, bits = 0, ac = 0;
const char *p;
u8 *cp = 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]);

for (i = 0; i < len; i++) {
p = strchr(lookup_table, src[i]);
if (p == NULL || src[i] == 0)
return -2;
ac += (p - lookup_table) << bits;
return -1;
ac = (ac << 6) | (p - base64url_table);
bits += 6;
if (bits >= 8) {
*cp++ = ac & 0xff;
ac >>= 8;
bits -= 8;
*bp++ = (u8)(ac >> bits);
}
}
if (ac)
if (ac & ((1 << bits) - 1))
return -1;
return cp - dst;
return bp - dst;
}

bool fscrypt_fname_encrypted_size(const union fscrypt_policy *policy,
Expand Down Expand Up @@ -263,10 +287,8 @@ bool fscrypt_fname_encrypted_size(const union fscrypt_policy *policy,
int fscrypt_fname_alloc_buffer(u32 max_encrypted_len,
struct fscrypt_str *crypto_str)
{
const u32 max_encoded_len = BASE64_CHARS(FSCRYPT_NOKEY_NAME_MAX);
u32 max_presented_len;

max_presented_len = max(max_encoded_len, max_encrypted_len);
u32 max_presented_len = max_t(u32, FSCRYPT_NOKEY_NAME_MAX_ENCODED,
max_encrypted_len);

crypto_str->name = kmalloc(max_presented_len + 1, GFP_NOFS);
if (!crypto_str->name)
Expand Down Expand Up @@ -342,7 +364,7 @@ int fscrypt_fname_disk_to_usr(const struct inode *inode,
offsetof(struct fscrypt_nokey_name, bytes));
BUILD_BUG_ON(offsetofend(struct fscrypt_nokey_name, bytes) !=
offsetof(struct fscrypt_nokey_name, sha256));
BUILD_BUG_ON(BASE64_CHARS(FSCRYPT_NOKEY_NAME_MAX) > NAME_MAX);
BUILD_BUG_ON(FSCRYPT_NOKEY_NAME_MAX_ENCODED > NAME_MAX);

nokey_name.dirhash[0] = hash;
nokey_name.dirhash[1] = minor_hash;
Expand All @@ -358,7 +380,8 @@ int fscrypt_fname_disk_to_usr(const struct inode *inode,
nokey_name.sha256);
size = FSCRYPT_NOKEY_NAME_MAX;
}
oname->len = base64_encode((const u8 *)&nokey_name, size, oname->name);
oname->len = fscrypt_base64url_encode((const u8 *)&nokey_name, size,
oname->name);
return 0;
}
EXPORT_SYMBOL(fscrypt_fname_disk_to_usr);
Expand Down Expand Up @@ -432,14 +455,15 @@ int fscrypt_setup_filename(struct inode *dir, const struct qstr *iname,
* user-supplied name
*/

if (iname->len > BASE64_CHARS(FSCRYPT_NOKEY_NAME_MAX))
if (iname->len > FSCRYPT_NOKEY_NAME_MAX_ENCODED)
return -ENOENT;

fname->crypto_buf.name = kmalloc(FSCRYPT_NOKEY_NAME_MAX, GFP_KERNEL);
if (fname->crypto_buf.name == NULL)
return -ENOMEM;

ret = base64_decode(iname->name, iname->len, fname->crypto_buf.name);
ret = fscrypt_base64url_decode(iname->name, iname->len,
fname->crypto_buf.name);
if (ret < (int)offsetof(struct fscrypt_nokey_name, bytes[1]) ||
(ret > offsetof(struct fscrypt_nokey_name, sha256) &&
ret != FSCRYPT_NOKEY_NAME_MAX)) {
Expand Down
44 changes: 44 additions & 0 deletions fs/crypto/hooks.c
Original file line number Diff line number Diff line change
Expand Up @@ -384,3 +384,47 @@ const char *fscrypt_get_symlink(struct inode *inode, const void *caddr,
return ERR_PTR(err);
}
EXPORT_SYMBOL_GPL(fscrypt_get_symlink);

/**
* fscrypt_symlink_getattr() - set the correct st_size for encrypted symlinks
* @path: the path for the encrypted symlink being queried
* @stat: the struct being filled with the symlink's attributes
*
* Override st_size of encrypted symlinks to be the length of the decrypted
* symlink target (or the no-key encoded symlink target, if the key is
* unavailable) rather than the length of the encrypted symlink target. This is
* necessary for st_size to match the symlink target that userspace actually
* sees. POSIX requires this, and some userspace programs depend on it.
*
* This requires reading the symlink target from disk if needed, setting up the
* inode's encryption key if possible, and then decrypting or encoding the
* symlink target. This makes lstat() more heavyweight than is normally the
* case. However, decrypted symlink targets will be cached in ->i_link, so
* usually the symlink won't have to be read and decrypted again later if/when
* it is actually followed, readlink() is called, or lstat() is called again.
*
* Return: 0 on success, -errno on failure
*/
int fscrypt_symlink_getattr(const struct path *path, struct kstat *stat)
{
struct dentry *dentry = path->dentry;
struct inode *inode = d_inode(dentry);
const char *link;
DEFINE_DELAYED_CALL(done);

/*
* To get the symlink target that userspace will see (whether it's the
* decrypted target or the no-key encoded target), we can just get it in
* the same way the VFS does during path resolution and readlink().
*/
link = READ_ONCE(inode->i_link);
if (!link) {
link = inode->i_op->get_link(dentry, inode, &done);
if (IS_ERR(link))
return PTR_ERR(link);
}
stat->size = strlen(link);
do_delayed_call(&done);
return 0;
}
EXPORT_SYMBOL_GPL(fscrypt_symlink_getattr);
12 changes: 11 additions & 1 deletion fs/ext4/symlink.c
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,20 @@ static const char *ext4_encrypted_get_link(struct dentry *dentry,
return paddr;
}

static int ext4_encrypted_symlink_getattr(struct user_namespace *mnt_userns,
const struct path *path,
struct kstat *stat, u32 request_mask,
unsigned int query_flags)
{
ext4_getattr(mnt_userns, path, stat, request_mask, query_flags);

return fscrypt_symlink_getattr(path, stat);
}

const struct inode_operations ext4_encrypted_symlink_inode_operations = {
.get_link = ext4_encrypted_get_link,
.setattr = ext4_setattr,
.getattr = ext4_getattr,
.getattr = ext4_encrypted_symlink_getattr,
.listxattr = ext4_listxattr,
};

Expand Down
12 changes: 11 additions & 1 deletion fs/f2fs/namei.c
Original file line number Diff line number Diff line change
Expand Up @@ -1323,9 +1323,19 @@ static const char *f2fs_encrypted_get_link(struct dentry *dentry,
return target;
}

static int f2fs_encrypted_symlink_getattr(struct user_namespace *mnt_userns,
const struct path *path,
struct kstat *stat, u32 request_mask,
unsigned int query_flags)
{
f2fs_getattr(mnt_userns, path, stat, request_mask, query_flags);

return fscrypt_symlink_getattr(path, stat);
}

const struct inode_operations f2fs_encrypted_symlink_inode_operations = {
.get_link = f2fs_encrypted_get_link,
.getattr = f2fs_getattr,
.getattr = f2fs_encrypted_symlink_getattr,
.setattr = f2fs_setattr,
.listxattr = f2fs_listxattr,
};
Expand Down
13 changes: 12 additions & 1 deletion fs/ubifs/file.c
Original file line number Diff line number Diff line change
Expand Up @@ -1630,6 +1630,17 @@ static const char *ubifs_get_link(struct dentry *dentry,
return fscrypt_get_symlink(inode, ui->data, ui->data_len, done);
}

static int ubifs_symlink_getattr(struct user_namespace *mnt_userns,
const struct path *path, struct kstat *stat,
u32 request_mask, unsigned int query_flags)
{
ubifs_getattr(mnt_userns, path, stat, request_mask, query_flags);

if (IS_ENCRYPTED(d_inode(path->dentry)))
return fscrypt_symlink_getattr(path, stat);
return 0;
}

const struct address_space_operations ubifs_file_address_operations = {
.readpage = ubifs_readpage,
.writepage = ubifs_writepage,
Expand All @@ -1655,7 +1666,7 @@ const struct inode_operations ubifs_file_inode_operations = {
const struct inode_operations ubifs_symlink_inode_operations = {
.get_link = ubifs_get_link,
.setattr = ubifs_setattr,
.getattr = ubifs_getattr,
.getattr = ubifs_symlink_getattr,
.listxattr = ubifs_listxattr,
.update_time = ubifs_update_time,
};
Expand Down
Loading

0 comments on commit cd35820

Please sign in to comment.