Release 4.15 fs/crypto/fname.c
// SPDX-License-Identifier: GPL-2.0
/*
* This contains functions for filename crypto management
*
* Copyright (C) 2015, Google, Inc.
* Copyright (C) 2015, Motorola Mobility
*
* Written by Uday Savagaonkar, 2014.
* Modified by Jaegeuk Kim, 2015.
*
* This has not yet undergone a rigorous security audit.
*/
#include <linux/scatterlist.h>
#include <linux/ratelimit.h>
#include "fscrypt_private.h"
/**
* fname_encrypt() - encrypt a filename
*
* The caller must have allocated sufficient memory for the @oname string.
*
* Return: 0 on success, -errno on failure
*/
static int fname_encrypt(struct inode *inode,
const struct qstr *iname, struct fscrypt_str *oname)
{
struct skcipher_request *req = NULL;
DECLARE_CRYPTO_WAIT(wait);
struct fscrypt_info *ci = inode->i_crypt_info;
struct crypto_skcipher *tfm = ci->ci_ctfm;
int res = 0;
char iv[FS_CRYPTO_BLOCK_SIZE];
struct scatterlist sg;
int padding = 4 << (ci->ci_flags & FS_POLICY_FLAGS_PAD_MASK);
unsigned int lim;
unsigned int cryptlen;
lim = inode->i_sb->s_cop->max_namelen(inode);
if (iname->len <= 0 || iname->len > lim)
return -EIO;
/*
* Copy the filename to the output buffer for encrypting in-place and
* pad it with the needed number of NUL bytes.
*/
cryptlen = max_t(unsigned int, iname->len, FS_CRYPTO_BLOCK_SIZE);
cryptlen = round_up(cryptlen, padding);
cryptlen = min(cryptlen, lim);
memcpy(oname->name, iname->name, iname->len);
memset(oname->name + iname->len, 0, cryptlen - iname->len);
/* Initialize the IV */
memset(iv, 0, FS_CRYPTO_BLOCK_SIZE);
/* Set up the encryption request */
req = skcipher_request_alloc(tfm, GFP_NOFS);
if (!req) {
printk_ratelimited(KERN_ERR
"%s: skcipher_request_alloc() failed\n", __func__);
return -ENOMEM;
}
skcipher_request_set_callback(req,
CRYPTO_TFM_REQ_MAY_BACKLOG | CRYPTO_TFM_REQ_MAY_SLEEP,
crypto_req_done, &wait);
sg_init_one(&sg, oname->name, cryptlen);
skcipher_request_set_crypt(req, &sg, &sg, cryptlen, iv);
/* Do the encryption */
res = crypto_wait_req(crypto_skcipher_encrypt(req), &wait);
skcipher_request_free(req);
if (res < 0) {
printk_ratelimited(KERN_ERR
"%s: Error (error code %d)\n", __func__, res);
return res;
}
oname->len = cryptlen;
return 0;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
| Jaegeuk Kim | 221 | 70.83% | 2 | 28.57% |
| Eric Biggers | 76 | 24.36% | 3 | 42.86% |
| Gilad Ben-Yossef | 8 | 2.56% | 1 | 14.29% |
| Herbert Xu | 7 | 2.24% | 1 | 14.29% |
| Total | 312 | 100.00% | 7 | 100.00% |
/**
* fname_decrypt() - decrypt a filename
*
* The caller must have allocated sufficient memory for the @oname string.
*
* Return: 0 on success, -errno on failure
*/
static int fname_decrypt(struct inode *inode,
const struct fscrypt_str *iname,
struct fscrypt_str *oname)
{
struct skcipher_request *req = NULL;
DECLARE_CRYPTO_WAIT(wait);
struct scatterlist src_sg, dst_sg;
struct fscrypt_info *ci = inode->i_crypt_info;
struct crypto_skcipher *tfm = ci->ci_ctfm;
int res = 0;
char iv[FS_CRYPTO_BLOCK_SIZE];
unsigned lim;
lim = inode->i_sb->s_cop->max_namelen(inode);
if (iname->len <= 0 || iname->len > lim)
return -EIO;
/* Allocate request */
req = skcipher_request_alloc(tfm, GFP_NOFS);
if (!req) {
printk_ratelimited(KERN_ERR
"%s: crypto_request_alloc() failed\n", __func__);
return -ENOMEM;
}
skcipher_request_set_callback(req,
CRYPTO_TFM_REQ_MAY_BACKLOG | CRYPTO_TFM_REQ_MAY_SLEEP,
crypto_req_done, &wait);
/* Initialize IV */
memset(iv, 0, FS_CRYPTO_BLOCK_SIZE);
/* Create decryption request */
sg_init_one(&src_sg, iname->name, iname->len);
sg_init_one(&dst_sg, oname->name, oname->len);
skcipher_request_set_crypt(req, &src_sg, &dst_sg, iname->len, iv);
res = crypto_wait_req(crypto_skcipher_decrypt(req), &wait);
skcipher_request_free(req);
if (res < 0) {
printk_ratelimited(KERN_ERR
"%s: Error (error code %d)\n", __func__, res);
return res;
}
oname->len = strnlen(oname->name, iname->len);
return 0;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
| Jaegeuk Kim | 243 | 93.82% | 2 | 40.00% |
| Gilad Ben-Yossef | 8 | 3.09% | 1 | 20.00% |
| Herbert Xu | 7 | 2.70% | 1 | 20.00% |
| Eric Biggers | 1 | 0.39% | 1 | 20.00% |
| Total | 259 | 100.00% | 5 | 100.00% |
static const char *lookup_table =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,";
#define BASE64_CHARS(nbytes) DIV_ROUND_UP((nbytes) * 4, 3)
/**
* digest_encode() -
*
* Encodes the input digest using characters from the set [a-zA-Z0-9_+].
* The encoded string is roughly 4/3 times the size of the input string.
*/
static int digest_encode(const char *src, int len, char *dst)
{
int i = 0, bits = 0, ac = 0;
char *cp = dst;
while (i < len) {
ac += (((unsigned char) src[i]) << bits);
bits += 8;
do {
*cp++ = lookup_table[ac & 0x3f];
ac >>= 6;
bits -= 6;
} while (bits >= 6);
i++;
}
if (bits)
*cp++ = lookup_table[ac & 0x3f];
return cp - dst;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
| Jaegeuk Kim | 118 | 100.00% | 1 | 100.00% |
| Total | 118 | 100.00% | 1 | 100.00% |
static int digest_decode(const char *src, int len, char *dst)
{
int i = 0, bits = 0, ac = 0;
const char *p;
char *cp = dst;
while (i < len) {
p = strchr(lookup_table, src[i]);
if (p == NULL || src[i] == 0)
return -2;
ac += (p - lookup_table) << bits;
bits += 6;
if (bits >= 8) {
*cp++ = ac & 0xff;
ac >>= 8;
bits -= 8;
}
i++;
}
if (ac)
return -1;
return cp - dst;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
| Jaegeuk Kim | 133 | 100.00% | 1 | 100.00% |
| Total | 133 | 100.00% | 1 | 100.00% |
u32 fscrypt_fname_encrypted_size(const struct inode *inode, u32 ilen)
{
int padding = 32;
struct fscrypt_info *ci = inode->i_crypt_info;
if (ci)
padding = 4 << (ci->ci_flags & FS_POLICY_FLAGS_PAD_MASK);
ilen = max(ilen, (u32)FS_CRYPTO_BLOCK_SIZE);
return round_up(ilen, padding);
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
| Jaegeuk Kim | 51 | 79.69% | 3 | 50.00% |
| Eric Biggers | 9 | 14.06% | 1 | 16.67% |
| Chao Yu | 3 | 4.69% | 1 | 16.67% |
| David Gstir | 1 | 1.56% | 1 | 16.67% |
| Total | 64 | 100.00% | 6 | 100.00% |
EXPORT_SYMBOL(fscrypt_fname_encrypted_size);
/**
* fscrypt_fname_crypto_alloc_obuff() -
*
* Allocates an output buffer that is sufficient for the crypto operation
* specified by the context and the direction.
*/
int fscrypt_fname_alloc_buffer(const struct inode *inode,
u32 ilen, struct fscrypt_str *crypto_str)
{
u32 olen = fscrypt_fname_encrypted_size(inode, ilen);
const u32 max_encoded_len =
max_t(u32, BASE64_CHARS(FSCRYPT_FNAME_MAX_UNDIGESTED_SIZE),
1 + BASE64_CHARS(sizeof(struct fscrypt_digested_name)));
crypto_str->len = olen;
olen = max(olen, max_encoded_len);
/*
* Allocated buffer can hold one more character to null-terminate the
* string
*/
crypto_str->name = kmalloc(olen + 1, GFP_NOFS);
if (!(crypto_str->name))
return -ENOMEM;
return 0;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
| Jaegeuk Kim | 43 | 43.43% | 2 | 40.00% |
| Eric Biggers | 32 | 32.32% | 1 | 20.00% |
| Chao Yu | 23 | 23.23% | 1 | 20.00% |
| David Gstir | 1 | 1.01% | 1 | 20.00% |
| Total | 99 | 100.00% | 5 | 100.00% |
EXPORT_SYMBOL(fscrypt_fname_alloc_buffer);
/**
* fscrypt_fname_crypto_free_buffer() -
*
* Frees the buffer allocated for crypto operation.
*/
void fscrypt_fname_free_buffer(struct fscrypt_str *crypto_str)
{
if (!crypto_str)
return;
kfree(crypto_str->name);
crypto_str->name = NULL;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
| Jaegeuk Kim | 29 | 100.00% | 2 | 100.00% |
| Total | 29 | 100.00% | 2 | 100.00% |
EXPORT_SYMBOL(fscrypt_fname_free_buffer);
/**
* fscrypt_fname_disk_to_usr() - converts a filename from disk space to user
* space
*
* The caller must have allocated sufficient memory for the @oname string.
*
* If the key is available, we'll decrypt the disk name; otherwise, we'll encode
* it for presentation. Short names are directly base64-encoded, while long
* names are encoded in fscrypt_digested_name format.
*
* Return: 0 on success, -errno on failure
*/
int fscrypt_fname_disk_to_usr(struct inode *inode,
u32 hash, u32 minor_hash,
const struct fscrypt_str *iname,
struct fscrypt_str *oname)
{
const struct qstr qname = FSTR_TO_QSTR(iname);
struct fscrypt_digested_name digested_name;
if (fscrypt_is_dot_dotdot(&qname)) {
oname->name[0] = '.';
oname->name[iname->len - 1] = '.';
oname->len = iname->len;
return 0;
}
if (iname->len < FS_CRYPTO_BLOCK_SIZE)
return -EUCLEAN;
if (inode->i_crypt_info)
return fname_decrypt(inode, iname, oname);
if (iname->len <= FSCRYPT_FNAME_MAX_UNDIGESTED_SIZE) {
oname->len = digest_encode(iname->name, iname->len,
oname->name);
return 0;
}
if (hash) {
digested_name.hash = hash;
digested_name.minor_hash = minor_hash;
} else {
digested_name.hash = 0;
digested_name.minor_hash = 0;
}
memcpy(digested_name.digest,
FSCRYPT_FNAME_DIGEST(iname->name, iname->len),
FSCRYPT_FNAME_DIGEST_SIZE);
oname->name[0] = '_';
oname->len = 1 + digest_encode((const char *)&digested_name,
sizeof(digested_name), oname->name + 1);
return 0;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
| Jaegeuk Kim | 185 | 78.06% | 3 | 60.00% |
| Eric Biggers | 52 | 21.94% | 2 | 40.00% |
| Total | 237 | 100.00% | 5 | 100.00% |
EXPORT_SYMBOL(fscrypt_fname_disk_to_usr);
/**
* fscrypt_fname_usr_to_disk() - converts a filename from user space to disk
* space
*
* The caller must have allocated sufficient memory for the @oname string.
*
* Return: 0 on success, -errno on failure
*/
int fscrypt_fname_usr_to_disk(struct inode *inode,
const struct qstr *iname,
struct fscrypt_str *oname)
{
if (fscrypt_is_dot_dotdot(iname)) {
oname->name[0] = '.';
oname->name[iname->len - 1] = '.';
oname->len = iname->len;
return 0;
}
if (inode->i_crypt_info)
return fname_encrypt(inode, iname, oname);
/*
* Without a proper key, a user is not allowed to modify the filenames
* in a directory. Consequently, a user space name cannot be mapped to
* a disk-space name
*/
return -ENOKEY;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
| Jaegeuk Kim | 82 | 97.62% | 2 | 50.00% |
| Eric Biggers | 2 | 2.38% | 2 | 50.00% |
| Total | 84 | 100.00% | 4 | 100.00% |
EXPORT_SYMBOL(fscrypt_fname_usr_to_disk);
/**
* fscrypt_setup_filename() - prepare to search a possibly encrypted directory
* @dir: the directory that will be searched
* @iname: the user-provided filename being searched for
* @lookup: 1 if we're allowed to proceed without the key because it's
* ->lookup() or we're finding the dir_entry for deletion; 0 if we cannot
* proceed without the key because we're going to create the dir_entry.
* @fname: the filename information to be filled in
*
* Given a user-provided filename @iname, this function sets @fname->disk_name
* to the name that would be stored in the on-disk directory entry, if possible.
* If the directory is unencrypted this is simply @iname. Else, if we have the
* directory's encryption key, then @iname is the plaintext, so we encrypt it to
* get the disk_name.
*
* Else, for keyless @lookup operations, @iname is the presented ciphertext, so
* we decode it to get either the ciphertext disk_name (for short names) or the
* fscrypt_digested_name (for long names). Non-@lookup operations will be
* impossible in this case, so we fail them with ENOKEY.
*
* If successful, fscrypt_free_filename() must be called later to clean up.
*
* Return: 0 on success, -errno on failure
*/
int fscrypt_setup_filename(struct inode *dir, const struct qstr *iname,
int lookup, struct fscrypt_name *fname)
{
int ret;
int digested;
memset(fname, 0, sizeof(struct fscrypt_name));
fname->usr_fname = iname;
if (!IS_ENCRYPTED(dir) || fscrypt_is_dot_dotdot(iname)) {
fname->disk_name.name = (unsigned char *)iname->name;
fname->disk_name.len = iname->len;
return 0;
}
ret = fscrypt_get_encryption_info(dir);
if (ret && ret != -EOPNOTSUPP)
return ret;
if (dir->i_crypt_info) {
ret = fscrypt_fname_alloc_buffer(dir, iname->len,
&fname->crypto_buf);
if (ret)
return ret;
ret = fname_encrypt(dir, iname, &fname->crypto_buf);
if (ret)
goto errout;
fname->disk_name.name = fname->crypto_buf.name;
fname->disk_name.len = fname->crypto_buf.len;
return 0;
}
if (!lookup)
return -ENOKEY;
/*
* 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 fscrypt_digested_name)))
return -ENOENT;
digested = 1;
} else {
if (iname->len >
BASE64_CHARS(FSCRYPT_FNAME_MAX_UNDIGESTED_SIZE))
return -ENOENT;
digested = 0;
}
fname->crypto_buf.name =
kmalloc(max_t(size_t, FSCRYPT_FNAME_MAX_UNDIGESTED_SIZE,
sizeof(struct fscrypt_digested_name)),
GFP_KERNEL);
if (fname->crypto_buf.name == NULL)
return -ENOMEM;
ret = digest_decode(iname->name + digested, iname->len - digested,
fname->crypto_buf.name);
if (ret < 0) {
ret = -ENOENT;
goto errout;
}
fname->crypto_buf.len = ret;
if (digested) {
const struct fscrypt_digested_name *n =
(const void *)fname->crypto_buf.name;
fname->hash = n->hash;
fname->minor_hash = n->minor_hash;
} else {
fname->disk_name.name = fname->crypto_buf.name;
fname->disk_name.len = fname->crypto_buf.len;
}
return 0;
errout:
fscrypt_fname_free_buffer(&fname->crypto_buf);
return ret;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
| Jaegeuk Kim | 333 | 77.99% | 3 | 37.50% |
| Eric Biggers | 77 | 18.03% | 4 | 50.00% |
| Chao Yu | 17 | 3.98% | 1 | 12.50% |
| Total | 427 | 100.00% | 8 | 100.00% |
EXPORT_SYMBOL(fscrypt_setup_filename);
Overall Contributors
| Person | Tokens | Prop | Commits | CommitProp |
| Jaegeuk Kim | 1488 | 81.49% | 5 | 26.32% |
| Eric Biggers | 261 | 14.29% | 7 | 36.84% |
| Chao Yu | 43 | 2.35% | 2 | 10.53% |
| Gilad Ben-Yossef | 16 | 0.88% | 1 | 5.26% |
| Herbert Xu | 14 | 0.77% | 1 | 5.26% |
| David Gstir | 2 | 0.11% | 1 | 5.26% |
| Theodore Y. Ts'o | 1 | 0.05% | 1 | 5.26% |
| Greg Kroah-Hartman | 1 | 0.05% | 1 | 5.26% |
| Total | 1826 | 100.00% | 19 | 100.00% |
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.