diff --git a/fs/ceph/crypto.c b/fs/ceph/crypto.c index 7493a3acd7d0..bc0a097a4cea 100644 --- a/fs/ceph/crypto.c +++ b/fs/ceph/crypto.c @@ -298,6 +298,10 @@ int ceph_encode_encrypted_dname(struct inode *parent, char *buf, int elen) * Otherwise, base64 decode the string, and then ask fscrypt to format it * for userland presentation. * + * Though the fscrypt/crypto subsystems broadly expect all buffers to be in the + * linear-mapped region, this function slightly relaxes those requirements: + * fname->ctext, fname->name, and oname->name may be vmalloc(), but not tname. + * * Returns 0 on success or negative error code on error. */ int ceph_fname_to_usr(const struct ceph_fname *fname, unsigned char *tname, @@ -305,11 +309,15 @@ int ceph_fname_to_usr(const struct ceph_fname *fname, unsigned char *tname, { struct inode *dir = fname->dir; struct fscrypt_str _tname = FSTR_INIT(NULL, 0); + struct fscrypt_str _oname; struct fscrypt_str iname; char *name = fname->name; int name_len = fname->name_len; int ret; + if (WARN_ON_ONCE(tname && is_vmalloc_addr(tname))) + return -EIO; + /* Sanity check that the resulting name will fit in the buffer */ if (fname->name_len > NAME_MAX || fname->ctext_len > NAME_MAX) return -EIO; @@ -350,16 +358,18 @@ int ceph_fname_to_usr(const struct ceph_fname *fname, unsigned char *tname, goto out_inode; } + if (!tname && (fname->ctext_len == 0 || + unlikely(is_vmalloc_addr(fname->ctext)) || + unlikely(is_vmalloc_addr(oname->name)))) { + ret = fscrypt_fname_alloc_buffer(NAME_MAX, &_tname); + if (ret) + goto out_inode; + tname = _tname.name; + } + if (fname->ctext_len == 0) { int declen; - if (!tname) { - ret = fscrypt_fname_alloc_buffer(NAME_MAX, &_tname); - if (ret) - goto out_inode; - tname = _tname.name; - } - declen = base64_decode(name, name_len, tname, false, BASE64_IMAP); if (declen <= 0) { ret = -EIO; @@ -367,13 +377,28 @@ int ceph_fname_to_usr(const struct ceph_fname *fname, unsigned char *tname, } iname.name = tname; iname.len = declen; + } else if (unlikely(is_vmalloc_addr(fname->ctext))) { + memcpy(tname, fname->ctext, fname->ctext_len); + + iname.name = tname; + iname.len = fname->ctext_len; } else { iname.name = fname->ctext; iname.len = fname->ctext_len; } - ret = fscrypt_fname_disk_to_usr(dir, 0, 0, &iname, oname); - if (!ret && (dir != fname->dir)) { + _oname.name = unlikely(is_vmalloc_addr(oname->name)) ? tname : oname->name; + _oname.len = oname->len; + + ret = fscrypt_fname_disk_to_usr(dir, 0, 0, &iname, &_oname); + if (ret) + goto out; + + if (unlikely(is_vmalloc_addr(oname->name))) + memcpy(oname->name, _oname.name, _oname.len); + oname->len = _oname.len; + + if (dir != fname->dir) { char tmp_buf[BASE64_CHARS(NAME_MAX)]; name_len = snprintf(tmp_buf, sizeof(tmp_buf), "_%.*s_%llu", diff --git a/fs/ceph/mds_client.c b/fs/ceph/mds_client.c index 80c72f295bcb..1cb95688c633 100644 --- a/fs/ceph/mds_client.c +++ b/fs/ceph/mds_client.c @@ -541,9 +541,13 @@ static int parse_reply_info_readdir(void **p, void *end, * to do the base64_decode in-place. It's * safe because the decoded string should * always be shorter, which is 3/4 of origin - * string. + * string. If this message was allocated with + * vmalloc() (happens, but rarely), leave it + * NULL and let ceph_fname_to_usr() allocate + * suitable temporary working space instead. */ - tname = _name; + if (likely(!is_vmalloc_addr(_name))) + tname = _name; /* * Set oname to _name too, and this will be