Skip to content

Commit dd66df0

Browse files
Luís Henriquesidryomov
authored andcommitted
ceph: add support for encrypted snapshot names
Since filenames in encrypted directories are encrypted and shown as a base64-encoded string when the directory is locked, make snapshot names show a similar behaviour. When creating a snapshot, .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 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. [ idryomov: drop const on !CONFIG_FS_ENCRYPTION branch too ] Signed-off-by: Luís Henriques <[email protected]> Reviewed-by: Jeff Layton <[email protected]> Reviewed-by: Xiubo Li <[email protected]> Reviewed-by: Milind Changire <[email protected]> Signed-off-by: Ilya Dryomov <[email protected]>
1 parent b422f11 commit dd66df0

File tree

3 files changed

+201
-41
lines changed

3 files changed

+201
-41
lines changed

fs/ceph/crypto.c

Lines changed: 166 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -193,17 +193,101 @@ void ceph_fscrypt_as_ctx_to_req(struct ceph_mds_request *req,
193193
swap(req->r_fscrypt_auth, as->fscrypt_auth);
194194
}
195195

196-
int ceph_encode_encrypted_dname(const struct inode *parent,
197-
struct qstr *d_name, char *buf)
196+
/*
197+
* User-created snapshots can't start with '_'. Snapshots that start with this
198+
* character are special (hint: there aren't real snapshots) and use the
199+
* following format:
200+
*
201+
* _<SNAPSHOT-NAME>_<INODE-NUMBER>
202+
*
203+
* where:
204+
* - <SNAPSHOT-NAME> - the real snapshot name that may need to be decrypted,
205+
* - <INODE-NUMBER> - the inode number (in decimal) for the actual snapshot
206+
*
207+
* This function parses these snapshot names and returns the inode
208+
* <INODE-NUMBER>. 'name_len' will also bet set with the <SNAPSHOT-NAME>
209+
* length.
210+
*/
211+
static struct inode *parse_longname(const struct inode *parent,
212+
const char *name, int *name_len)
198213
{
214+
struct inode *dir = NULL;
215+
struct ceph_vino vino = { .snap = CEPH_NOSNAP };
216+
char *inode_number;
217+
char *name_end;
218+
int orig_len = *name_len;
219+
int ret = -EIO;
220+
221+
/* Skip initial '_' */
222+
name++;
223+
name_end = strrchr(name, '_');
224+
if (!name_end) {
225+
dout("Failed to parse long snapshot name: %s\n", name);
226+
return ERR_PTR(-EIO);
227+
}
228+
*name_len = (name_end - name);
229+
if (*name_len <= 0) {
230+
pr_err("Failed to parse long snapshot name\n");
231+
return ERR_PTR(-EIO);
232+
}
233+
234+
/* Get the inode number */
235+
inode_number = kmemdup_nul(name_end + 1,
236+
orig_len - *name_len - 2,
237+
GFP_KERNEL);
238+
if (!inode_number)
239+
return ERR_PTR(-ENOMEM);
240+
ret = kstrtou64(inode_number, 10, &vino.ino);
241+
if (ret) {
242+
dout("Failed to parse inode number: %s\n", name);
243+
dir = ERR_PTR(ret);
244+
goto out;
245+
}
246+
247+
/* And finally the inode */
248+
dir = ceph_find_inode(parent->i_sb, vino);
249+
if (!dir) {
250+
/* This can happen if we're not mounting cephfs on the root */
251+
dir = ceph_get_inode(parent->i_sb, vino, NULL);
252+
if (!dir)
253+
dir = ERR_PTR(-ENOENT);
254+
}
255+
if (IS_ERR(dir))
256+
dout("Can't find inode %s (%s)\n", inode_number, name);
257+
258+
out:
259+
kfree(inode_number);
260+
return dir;
261+
}
262+
263+
int ceph_encode_encrypted_dname(struct inode *parent, struct qstr *d_name,
264+
char *buf)
265+
{
266+
struct inode *dir = parent;
267+
struct qstr iname;
199268
u32 len;
269+
int name_len;
200270
int elen;
201271
int ret;
202-
u8 *cryptbuf;
272+
u8 *cryptbuf = NULL;
273+
274+
iname.name = d_name->name;
275+
name_len = d_name->len;
276+
277+
/* Handle the special case of snapshot names that start with '_' */
278+
if ((ceph_snap(dir) == CEPH_SNAPDIR) && (name_len > 0) &&
279+
(iname.name[0] == '_')) {
280+
dir = parse_longname(parent, iname.name, &name_len);
281+
if (IS_ERR(dir))
282+
return PTR_ERR(dir);
283+
iname.name++; /* skip initial '_' */
284+
}
285+
iname.len = name_len;
203286

204-
if (!fscrypt_has_encryption_key(parent)) {
287+
if (!fscrypt_has_encryption_key(dir)) {
205288
memcpy(buf, d_name->name, d_name->len);
206-
return d_name->len;
289+
elen = d_name->len;
290+
goto out;
207291
}
208292

209293
/*
@@ -212,19 +296,23 @@ int ceph_encode_encrypted_dname(const struct inode *parent,
212296
*
213297
* See: fscrypt_setup_filename
214298
*/
215-
if (!fscrypt_fname_encrypted_size(parent, d_name->len, NAME_MAX, &len))
216-
return -ENAMETOOLONG;
299+
if (!fscrypt_fname_encrypted_size(dir, iname.len, NAME_MAX, &len)) {
300+
elen = -ENAMETOOLONG;
301+
goto out;
302+
}
217303

218304
/* Allocate a buffer appropriate to hold the result */
219305
cryptbuf = kmalloc(len > CEPH_NOHASH_NAME_MAX ? NAME_MAX : len,
220306
GFP_KERNEL);
221-
if (!cryptbuf)
222-
return -ENOMEM;
307+
if (!cryptbuf) {
308+
elen = -ENOMEM;
309+
goto out;
310+
}
223311

224-
ret = fscrypt_fname_encrypt(parent, d_name, cryptbuf, len);
312+
ret = fscrypt_fname_encrypt(dir, &iname, cryptbuf, len);
225313
if (ret) {
226-
kfree(cryptbuf);
227-
return ret;
314+
elen = ret;
315+
goto out;
228316
}
229317

230318
/* hash the end if the name is long enough */
@@ -243,13 +331,31 @@ int ceph_encode_encrypted_dname(const struct inode *parent,
243331

244332
/* base64 encode the encrypted name */
245333
elen = ceph_base64_encode(cryptbuf, len, buf);
246-
kfree(cryptbuf);
247334
dout("base64-encoded ciphertext name = %.*s\n", elen, buf);
335+
336+
/* To understand the 240 limit, see CEPH_NOHASH_NAME_MAX comments */
337+
WARN_ON(elen > 240);
338+
if ((elen > 0) && (dir != parent)) {
339+
char tmp_buf[NAME_MAX];
340+
341+
elen = snprintf(tmp_buf, sizeof(tmp_buf), "_%.*s_%ld",
342+
elen, buf, dir->i_ino);
343+
memcpy(buf, tmp_buf, elen);
344+
}
345+
346+
out:
347+
kfree(cryptbuf);
348+
if (dir != parent) {
349+
if ((dir->i_state & I_NEW))
350+
discard_new_inode(dir);
351+
else
352+
iput(dir);
353+
}
248354
return elen;
249355
}
250356

251-
int ceph_encode_encrypted_fname(const struct inode *parent,
252-
struct dentry *dentry, char *buf)
357+
int ceph_encode_encrypted_fname(struct inode *parent, struct dentry *dentry,
358+
char *buf)
253359
{
254360
WARN_ON_ONCE(!fscrypt_has_encryption_key(parent));
255361

@@ -274,37 +380,51 @@ int ceph_encode_encrypted_fname(const struct inode *parent,
274380
int ceph_fname_to_usr(const struct ceph_fname *fname, struct fscrypt_str *tname,
275381
struct fscrypt_str *oname, bool *is_nokey)
276382
{
277-
int ret;
383+
struct inode *dir = fname->dir;
278384
struct fscrypt_str _tname = FSTR_INIT(NULL, 0);
279385
struct fscrypt_str iname;
280-
281-
if (!IS_ENCRYPTED(fname->dir)) {
282-
oname->name = fname->name;
283-
oname->len = fname->name_len;
284-
return 0;
285-
}
386+
char *name = fname->name;
387+
int name_len = fname->name_len;
388+
int ret;
286389

287390
/* Sanity check that the resulting name will fit in the buffer */
288391
if (fname->name_len > NAME_MAX || fname->ctext_len > NAME_MAX)
289392
return -EIO;
290393

291-
ret = ceph_fscrypt_prepare_readdir(fname->dir);
292-
if (ret < 0)
293-
return ret;
394+
/* Handle the special case of snapshot names that start with '_' */
395+
if ((ceph_snap(dir) == CEPH_SNAPDIR) && (name_len > 0) &&
396+
(name[0] == '_')) {
397+
dir = parse_longname(dir, name, &name_len);
398+
if (IS_ERR(dir))
399+
return PTR_ERR(dir);
400+
name++; /* skip initial '_' */
401+
}
402+
403+
if (!IS_ENCRYPTED(dir)) {
404+
oname->name = fname->name;
405+
oname->len = fname->name_len;
406+
ret = 0;
407+
goto out_inode;
408+
}
409+
410+
ret = ceph_fscrypt_prepare_readdir(dir);
411+
if (ret)
412+
goto out_inode;
294413

295414
/*
296415
* Use the raw dentry name as sent by the MDS instead of
297416
* generating a nokey name via fscrypt.
298417
*/
299-
if (!fscrypt_has_encryption_key(fname->dir)) {
418+
if (!fscrypt_has_encryption_key(dir)) {
300419
if (fname->no_copy)
301420
oname->name = fname->name;
302421
else
303422
memcpy(oname->name, fname->name, fname->name_len);
304423
oname->len = fname->name_len;
305424
if (is_nokey)
306425
*is_nokey = true;
307-
return 0;
426+
ret = 0;
427+
goto out_inode;
308428
}
309429

310430
if (fname->ctext_len == 0) {
@@ -313,12 +433,11 @@ int ceph_fname_to_usr(const struct ceph_fname *fname, struct fscrypt_str *tname,
313433
if (!tname) {
314434
ret = fscrypt_fname_alloc_buffer(NAME_MAX, &_tname);
315435
if (ret)
316-
return ret;
436+
goto out_inode;
317437
tname = &_tname;
318438
}
319439

320-
declen = ceph_base64_decode(fname->name, fname->name_len,
321-
tname->name);
440+
declen = ceph_base64_decode(name, name_len, tname->name);
322441
if (declen <= 0) {
323442
ret = -EIO;
324443
goto out;
@@ -330,9 +449,25 @@ int ceph_fname_to_usr(const struct ceph_fname *fname, struct fscrypt_str *tname,
330449
iname.len = fname->ctext_len;
331450
}
332451

333-
ret = fscrypt_fname_disk_to_usr(fname->dir, 0, 0, &iname, oname);
452+
ret = fscrypt_fname_disk_to_usr(dir, 0, 0, &iname, oname);
453+
if (!ret && (dir != fname->dir)) {
454+
char tmp_buf[CEPH_BASE64_CHARS(NAME_MAX)];
455+
456+
name_len = snprintf(tmp_buf, sizeof(tmp_buf), "_%.*s_%ld",
457+
oname->len, oname->name, dir->i_ino);
458+
memcpy(oname->name, tmp_buf, name_len);
459+
oname->len = name_len;
460+
}
461+
334462
out:
335463
fscrypt_fname_free_buffer(&_tname);
464+
out_inode:
465+
if ((dir != fname->dir) && !IS_ERR(dir)) {
466+
if ((dir->i_state & I_NEW))
467+
discard_new_inode(dir);
468+
else
469+
iput(dir);
470+
}
336471
return ret;
337472
}
338473

fs/ceph/crypto.h

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,10 @@ int ceph_fscrypt_prepare_context(struct inode *dir, struct inode *inode,
102102
struct ceph_acl_sec_ctx *as);
103103
void ceph_fscrypt_as_ctx_to_req(struct ceph_mds_request *req,
104104
struct ceph_acl_sec_ctx *as);
105-
int ceph_encode_encrypted_dname(const struct inode *parent,
106-
struct qstr *d_name, char *buf);
107-
int ceph_encode_encrypted_fname(const struct inode *parent,
108-
struct dentry *dentry, char *buf);
105+
int ceph_encode_encrypted_dname(struct inode *parent, struct qstr *d_name,
106+
char *buf);
107+
int ceph_encode_encrypted_fname(struct inode *parent, struct dentry *dentry,
108+
char *buf);
109109

110110
static inline int ceph_fname_alloc_buffer(struct inode *parent,
111111
struct fscrypt_str *fname)
@@ -194,14 +194,14 @@ static inline void ceph_fscrypt_as_ctx_to_req(struct ceph_mds_request *req,
194194
{
195195
}
196196

197-
static inline int ceph_encode_encrypted_dname(const struct inode *parent,
197+
static inline int ceph_encode_encrypted_dname(struct inode *parent,
198198
struct qstr *d_name, char *buf)
199199
{
200200
memcpy(buf, d_name->name, d_name->len);
201201
return d_name->len;
202202
}
203203

204-
static inline int ceph_encode_encrypted_fname(const struct inode *parent,
204+
static inline int ceph_encode_encrypted_fname(struct inode *parent,
205205
struct dentry *dentry, char *buf)
206206
{
207207
return -EOPNOTSUPP;

fs/ceph/inode.c

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,15 @@ struct inode *ceph_new_inode(struct inode *dir, struct dentry *dentry,
9191
if (err < 0)
9292
goto out_err;
9393

94-
err = ceph_fscrypt_prepare_context(dir, inode, as_ctx);
95-
if (err)
96-
goto out_err;
94+
/*
95+
* We'll skip setting fscrypt context for snapshots, leaving that for
96+
* the handle_reply().
97+
*/
98+
if (ceph_snap(dir) != CEPH_SNAPDIR) {
99+
err = ceph_fscrypt_prepare_context(dir, inode, as_ctx);
100+
if (err)
101+
goto out_err;
102+
}
97103

98104
return inode;
99105
out_err:
@@ -159,6 +165,7 @@ struct inode *ceph_get_snapdir(struct inode *parent)
159165
};
160166
struct inode *inode = ceph_get_inode(parent->i_sb, vino, NULL);
161167
struct ceph_inode_info *ci = ceph_inode(inode);
168+
int ret = -ENOTDIR;
162169

163170
if (IS_ERR(inode))
164171
return inode;
@@ -184,6 +191,24 @@ struct inode *ceph_get_snapdir(struct inode *parent)
184191
ci->i_rbytes = 0;
185192
ci->i_btime = ceph_inode(parent)->i_btime;
186193

194+
#ifdef CONFIG_FS_ENCRYPTION
195+
/* if encrypted, just borrow fscrypt_auth from parent */
196+
if (IS_ENCRYPTED(parent)) {
197+
struct ceph_inode_info *pci = ceph_inode(parent);
198+
199+
ci->fscrypt_auth = kmemdup(pci->fscrypt_auth,
200+
pci->fscrypt_auth_len,
201+
GFP_KERNEL);
202+
if (ci->fscrypt_auth) {
203+
inode->i_flags |= S_ENCRYPTED;
204+
ci->fscrypt_auth_len = pci->fscrypt_auth_len;
205+
} else {
206+
dout("Failed to alloc snapdir fscrypt_auth\n");
207+
ret = -ENOMEM;
208+
goto err;
209+
}
210+
}
211+
#endif
187212
if (inode->i_state & I_NEW) {
188213
inode->i_op = &ceph_snapdir_iops;
189214
inode->i_fop = &ceph_snapdir_fops;
@@ -197,7 +222,7 @@ struct inode *ceph_get_snapdir(struct inode *parent)
197222
discard_new_inode(inode);
198223
else
199224
iput(inode);
200-
return ERR_PTR(-ENOTDIR);
225+
return ERR_PTR(ret);
201226
}
202227

203228
const struct inode_operations ceph_file_iops = {

0 commit comments

Comments
 (0)