diff options
| author | Eric Biggers <ebiggers@google.com> | 2019-04-11 17:32:15 -0400 |
|---|---|---|
| committer | Theodore Ts'o <tytso@mit.edu> | 2019-04-16 18:57:09 -0400 |
| commit | e37a784d8b6a1e726de5ddc7b4809c086a08db09 (patch) | |
| tree | 965d40cec69107aba4324f72db93d896b12d0737 | |
| parent | ff5d3a97075c65731a46453d36e75b9cf925e165 (diff) | |
fscrypt: use READ_ONCE() to access ->i_crypt_info
->i_crypt_info starts out NULL and may later be locklessly set to a
non-NULL value by the cmpxchg() in fscrypt_get_encryption_info().
But ->i_crypt_info is used directly, which technically is incorrect.
It's a data race, and it doesn't include the data dependency barrier
needed to safely dereference the pointer on at least one architecture.
Fix this by using READ_ONCE() instead. Note: we don't need to use
smp_load_acquire(), since dereferencing the pointer only requires a data
dependency barrier, which is already included in READ_ONCE(). We also
don't need READ_ONCE() in places where ->i_crypt_info is unconditionally
dereferenced, since it must have already been checked.
Also downgrade the cmpxchg() to cmpxchg_release(), since RELEASE
semantics are sufficient on the write side.
Signed-off-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
| -rw-r--r-- | fs/crypto/crypto.c | 2 | ||||
| -rw-r--r-- | fs/crypto/fname.c | 4 | ||||
| -rw-r--r-- | fs/crypto/keyinfo.c | 4 | ||||
| -rw-r--r-- | fs/crypto/policy.c | 6 | ||||
| -rw-r--r-- | include/linux/fscrypt.h | 3 |
5 files changed, 10 insertions, 9 deletions
diff --git a/fs/crypto/crypto.c b/fs/crypto/crypto.c index 5efc494a4e38..098807788257 100644 --- a/fs/crypto/crypto.c +++ b/fs/crypto/crypto.c | |||
| @@ -328,7 +328,7 @@ static int fscrypt_d_revalidate(struct dentry *dentry, unsigned int flags) | |||
| 328 | spin_lock(&dentry->d_lock); | 328 | spin_lock(&dentry->d_lock); |
| 329 | cached_with_key = dentry->d_flags & DCACHE_ENCRYPTED_WITH_KEY; | 329 | cached_with_key = dentry->d_flags & DCACHE_ENCRYPTED_WITH_KEY; |
| 330 | spin_unlock(&dentry->d_lock); | 330 | spin_unlock(&dentry->d_lock); |
| 331 | dir_has_key = (d_inode(dir)->i_crypt_info != NULL); | 331 | dir_has_key = fscrypt_has_encryption_key(d_inode(dir)); |
| 332 | dput(dir); | 332 | dput(dir); |
| 333 | 333 | ||
| 334 | /* | 334 | /* |
diff --git a/fs/crypto/fname.c b/fs/crypto/fname.c index 7ff40a73dbec..050384c79f40 100644 --- a/fs/crypto/fname.c +++ b/fs/crypto/fname.c | |||
| @@ -269,7 +269,7 @@ int fscrypt_fname_disk_to_usr(struct inode *inode, | |||
| 269 | if (iname->len < FS_CRYPTO_BLOCK_SIZE) | 269 | if (iname->len < FS_CRYPTO_BLOCK_SIZE) |
| 270 | return -EUCLEAN; | 270 | return -EUCLEAN; |
| 271 | 271 | ||
| 272 | if (inode->i_crypt_info) | 272 | if (fscrypt_has_encryption_key(inode)) |
| 273 | return fname_decrypt(inode, iname, oname); | 273 | return fname_decrypt(inode, iname, oname); |
| 274 | 274 | ||
| 275 | if (iname->len <= FSCRYPT_FNAME_MAX_UNDIGESTED_SIZE) { | 275 | if (iname->len <= FSCRYPT_FNAME_MAX_UNDIGESTED_SIZE) { |
| @@ -336,7 +336,7 @@ int fscrypt_setup_filename(struct inode *dir, const struct qstr *iname, | |||
| 336 | if (ret) | 336 | if (ret) |
| 337 | return ret; | 337 | return ret; |
| 338 | 338 | ||
| 339 | if (dir->i_crypt_info) { | 339 | if (fscrypt_has_encryption_key(dir)) { |
| 340 | if (!fscrypt_fname_encrypted_size(dir, iname->len, | 340 | if (!fscrypt_fname_encrypted_size(dir, iname->len, |
| 341 | dir->i_sb->s_cop->max_namelen, | 341 | dir->i_sb->s_cop->max_namelen, |
| 342 | &fname->crypto_buf.len)) | 342 | &fname->crypto_buf.len)) |
diff --git a/fs/crypto/keyinfo.c b/fs/crypto/keyinfo.c index 322ce9686bdb..bf291c10c682 100644 --- a/fs/crypto/keyinfo.c +++ b/fs/crypto/keyinfo.c | |||
| @@ -509,7 +509,7 @@ int fscrypt_get_encryption_info(struct inode *inode) | |||
| 509 | u8 *raw_key = NULL; | 509 | u8 *raw_key = NULL; |
| 510 | int res; | 510 | int res; |
| 511 | 511 | ||
| 512 | if (inode->i_crypt_info) | 512 | if (fscrypt_has_encryption_key(inode)) |
| 513 | return 0; | 513 | return 0; |
| 514 | 514 | ||
| 515 | res = fscrypt_initialize(inode->i_sb->s_cop->flags); | 515 | res = fscrypt_initialize(inode->i_sb->s_cop->flags); |
| @@ -573,7 +573,7 @@ int fscrypt_get_encryption_info(struct inode *inode) | |||
| 573 | if (res) | 573 | if (res) |
| 574 | goto out; | 574 | goto out; |
| 575 | 575 | ||
| 576 | if (cmpxchg(&inode->i_crypt_info, NULL, crypt_info) == NULL) | 576 | if (cmpxchg_release(&inode->i_crypt_info, NULL, crypt_info) == NULL) |
| 577 | crypt_info = NULL; | 577 | crypt_info = NULL; |
| 578 | out: | 578 | out: |
| 579 | if (res == -ENOKEY) | 579 | if (res == -ENOKEY) |
diff --git a/fs/crypto/policy.c b/fs/crypto/policy.c index bd7eaf9b3f00..d536889ac31b 100644 --- a/fs/crypto/policy.c +++ b/fs/crypto/policy.c | |||
| @@ -194,8 +194,8 @@ int fscrypt_has_permitted_context(struct inode *parent, struct inode *child) | |||
| 194 | res = fscrypt_get_encryption_info(child); | 194 | res = fscrypt_get_encryption_info(child); |
| 195 | if (res) | 195 | if (res) |
| 196 | return 0; | 196 | return 0; |
| 197 | parent_ci = parent->i_crypt_info; | 197 | parent_ci = READ_ONCE(parent->i_crypt_info); |
| 198 | child_ci = child->i_crypt_info; | 198 | child_ci = READ_ONCE(child->i_crypt_info); |
| 199 | 199 | ||
| 200 | if (parent_ci && child_ci) { | 200 | if (parent_ci && child_ci) { |
| 201 | return memcmp(parent_ci->ci_master_key_descriptor, | 201 | return memcmp(parent_ci->ci_master_key_descriptor, |
| @@ -246,7 +246,7 @@ int fscrypt_inherit_context(struct inode *parent, struct inode *child, | |||
| 246 | if (res < 0) | 246 | if (res < 0) |
| 247 | return res; | 247 | return res; |
| 248 | 248 | ||
| 249 | ci = parent->i_crypt_info; | 249 | ci = READ_ONCE(parent->i_crypt_info); |
| 250 | if (ci == NULL) | 250 | if (ci == NULL) |
| 251 | return -ENOKEY; | 251 | return -ENOKEY; |
| 252 | 252 | ||
diff --git a/include/linux/fscrypt.h b/include/linux/fscrypt.h index 6cf8a34523ff..ec8ab7108599 100644 --- a/include/linux/fscrypt.h +++ b/include/linux/fscrypt.h | |||
| @@ -79,7 +79,8 @@ struct fscrypt_ctx { | |||
| 79 | 79 | ||
| 80 | static inline bool fscrypt_has_encryption_key(const struct inode *inode) | 80 | static inline bool fscrypt_has_encryption_key(const struct inode *inode) |
| 81 | { | 81 | { |
| 82 | return (inode->i_crypt_info != NULL); | 82 | /* pairs with cmpxchg_release() in fscrypt_get_encryption_info() */ |
| 83 | return READ_ONCE(inode->i_crypt_info) != NULL; | ||
| 83 | } | 84 | } |
| 84 | 85 | ||
| 85 | static inline bool fscrypt_dummy_context_enabled(struct inode *inode) | 86 | static inline bool fscrypt_dummy_context_enabled(struct inode *inode) |
