78
February 14, 2014
PEFS Security Audit
This is the result of a short 13-hour security audit of Private Encrypted File System (PEFS).
Thanks to Matt Olander for funding this audit.
-----------------------------------------------------------------------
PEFS Security Audit
Taylor Hornby
February 07, 2014
-----------------------------------------------------------------------
1. Introduction
This report documents the results of a 13-hour security audit on
Private Encrypted File System (PEFS). The audit uncovered several
minor problems, some of which may compromise confidentiality.
1.1. What is PEFS?
PEFS [1] is an encrypted filesystem similar to EncFS and eCryptFS.
PEFS is unlike block-level encryption (e.g. TrueCrypt) in that the
ciphertext mirrors the directory structure of the plaintext.
1.2. Audit Scope
The audit was performed by reading the PEFS source code [2]. The git
commit hash of the code that was audited is:
fb7c4a188cfa7ba69987a1a61104c56f63db5395
This audit focused primarily on PEFS's cryptographic design and
implementation. This includes PEFS's use of crypto primitives, but
not the implementations of the primitives themselves (i.e. the AES
implementation). Some integer overflow issues were discovered, but
looking for memory corruption vulnerabilities was not a priority of
the audit. Bugs in the file system implementation (pefs_vnops.c) were
also out of scope.
2. Issues
This section covers all of the security issues discovered during the
audit. They are each given a rating in three variables:
Exploitability: How hard is it for an attacker to exploit?
Security Impact: How bad is it if an attacker can exploit it?
Confirmation: Has the vulnerability been confirmed to exist?
2.1. HMAC and Other Secrets Not Compared in Constant Time
Exploitability: Low
Security Impact: Low
Confirmation: Confirmed by inspecting the code.
On line 357 of pefs_key.c, memcmp() is used to verify an HMAC. This
introduces a timing attack that an attacker might be able to use to
compute the HMAC of an arbitrary message. This is the HMAC used when
encrypting child keys for the key chains feature, not the VMAC (which
is compared in constant time).
There are several other non-constant-time comparisons. I am not sure
if they are relevant to security:
- The comparison of ptk_tweak in pefs_tkey_cmp() in pefs_vnops.c.
- Comparison of what might be cleartext filenames in
pefs_enccn_parsedir() in pefs_vnops.c.
- The comparisons of pk_keyid in pefs_crypto.c, pefs_ctl.c, and
pefs_keychain.c.
In general, when secrets are compared for equality, it should be done
in constant time so that the running time does not leak information
about their actual values.
2.2. Same Key Used for HMAC and Encryption
Exploitability: Very Low
Security Impact: Low
Confirmation: Confirmed by inspecting the code.
This issue affects the key chain system, which is an optional PEFS
feature. If the key chains feature is not used, the file encryption
is unaffected.
The same key is used for encryption and authentication in pefs_key.c.
This happens in the pefs_key_cipher() procedure. The variable 'key'
is created by HMACing a distinguisher string with another key called
pxk_key. The resulting 'key' is used twice: Once to encrypt the data,
and a second time to HMAC the ciphertext.
This does not create immediate problems, but using the same key for
multiple purposes is a bad practice. Instead, use HKDF to generate
two separate keys for the two different purposes.
2.3. Zero IV Used with CTR Mode in pefs_key.c
Exploitability: Medium (Depends on the use case.)
Security Impact: High
Confirmation: Confirmed by inspecting the code.
This issue affects the key chain system, which is an optional PEFS
feature. If the key chains feature is not used, the file encryption
is unaffected.
The pefs_key_cipher() procedure in pefs_key.c encrypts data using AES
in CTR mode with a zero IV. This means that if multiple messages are
encrypted with the same key, they are XORed with the same keystream.
Here's an example of how this can be exploited to reveal the context
of another user's files:
The key chain graph looks like:
A -> Z
A -> S
B -> S
The 'Z' key is encrypted with a parent key that only Alice knows.
The 'S' key is encrypted with Alice's parent key as well. Bob also
has access to 'S'.
In this scenario, Bob knows S, so he can XOR the known value of
S with Alice's encryption of S to get the key stream generated by
Alice's parent key. Bob can then XOR this with Alice's Z ciphertext
to get Z. If Bob did not know S, he could learn Z XOR S by XORing the
two ciphertexts together.
This is a problem whenever a parent key encrypts two or more children
keys. Instead of using a NULL IV, a random IV should be generated at
encryption time and stored in the ciphertext (remember to HMAC the IV
too!).
2.4. No Salt Used When Hashing Password
Exploitability: Medium
Security Impact: Medium
Confirmation: Confirmed by inspecting the code.
When deriving a key from a password, PEFS does not use salt. This is
necessary, otherwise an attacker can use pre-computation attacks
(lookup tables, rainbow tables, etc.) to significantly speed up the
process of cracking PEFS-encrypted directories with weak passwords.
Not using a salt was an explicit design decision to avoid having to
store metadata. I recommend adding an (optional?) mode with metadata
to support having a salt, so that if it fits the user's use case,
they can benefit from the additional security.
2.5. Ambiguous Filename Encryption Padding
Exploitability: Very Low
Security Impact: Very Low
Confirmation: Confirmed by inspecting the source code.
The filename encryption routine pads the plaintext with null bytes.
This is fine, since file names can't contain null bytes in UNIX, but
if this code is reused for something else, it could become a problem.
Pad the plaintext unambiguously by using a padding mode like PKCS#7.
2.6. Filename Encryption is Not Randomized When Files are Renamed
Exploitability: Medium (Depends on how the user renames files.)
Security Impact: Low
Confirmation: Confirmed by inspecting the source code.
PEFS uses a random 64-bit value, called the tweak, to differentiate
between encrypted files. This value is actually re-used for three
things. It's used as part of the XTS mode tweak, to "randomize" the
filename encryption, and as the VMAC IV (after being encrypted).
For the filename encryption, the tweak value is used as a substitute
for an IV. Filenames are encrypted by first prefixing the tweak, then
encrypting them in CBC mode with a zero IV.
This is a problem, because the tweak does not change when files are
renamed. With CBC mode, the IV has to be chosen randomly at the time
of encryption, or it isn't IND-CPA secure. If the filename is
changed, the new ciphertext will be identical to the old ciphertext
up to the block that contains the first difference. See the third
part of this image for an awesome visual demonstration:
https://pbs.twimg.com/media/BekWHdVCYAAgsSm.jpg:large
There are plans to solve this problem by encrypting filenames with
wide-block encryption like EME2. This should be an adequate solution,
but it needs more analysis.
There may be other problems with the tweak design as well. More
analysis is recommended in Section 5.3
2.7. Part of Files Encrypted with CTR Mode
Exploitability: High
Security Impact: Medium-High
Confirmation: Confirmed by inspecting the code.
If a file's size in bytes is not evenly divisible by 16, the last
block of bytes (up to 15 bytes) is encrypted with CTR mode. If the
last block is ever changed, and an attacker has the ciphertexts from
before and after the change, they can XOR both ciphertexts together
to learn the XOR of the old and new plaintexts. If the attacker knows
(or can guess) the old plaintext, they can get the new plaintext.
The relevant code is in xts_smallblock() in pefs_xts.c.
The only good remediation for this (that I can think of) involves
increasing the file size so that it is divisible by 16 bytes. I think
the severity vulnerability justifies the increase in complexity.
2.8. gf_mul128() Does Not Execute In Constant Time
Exploitability: Low
Security Impact: Low
Confirmation: Confirmed by inspecting the source code.
The gf_mul128() procedure, part of the XTS implementation in
pefs_xts.c, branches on a value related to its input, which could
enable side channel attacks:
static __inline void
gf_mul128(uint64_t *dst, const uint64_t *src)
{
static const uint8_t gf_128_fdbk = 0x87;
int carry;
carry = shl128(dst, src);
if (carry != 0) // <-- time difference could leak 'carry'
((uint8_t *)dst)[0] ^= gf_128_fdbk;
}
I don't know if this leaks enough information to break the encryption
(very probably not), but it's better to be safe and do it in constant
time anyway. A constant time conditional XOR is given in [3].
2.9. Memory Corruption / Integer Overflows
Exploitability: Unknown
Security Impact: High (If exploitable)
Confirmation: Not confirmed.
I noticed several bits of code with integer overflow and
signed/unsigned problems. I didn't have time to check if any of these
are exploitable, or are even values controlled by the attacker, but
I will list them here to err on the side of caution:
- In pefs_name_encrypt(), the "+ 1" in the file name size check
could overflow the integer and make the check pass even if
the filename is too long:
r = PEFS_NAME_NTOP_SIZE(pefs_name_padsize(size)) + 1;
if (r > MAXNAMLEN) {
return (-ENAMETOOLONG);
}
- In pefs_enccn_set(), the variable encname_len is a *signed*
integer. It is checked to be less than MAXPATHLEN then passed to
memcpy(). When it gets passed to memcpy(), it is cast to
a size_t, which is unsigned. If encname_len has a negative value,
it will pass the first check, then memcpy will interpret it as
a large unsigned value:
if (encname_len >= MAXPATHLEN)
panic("pefs_enccn_set: invalid encrypted name length: %d",
encname_len);
// ...
memcpy(pec->pec_buf, encname, encname_len);
((char *) pec->pec_buf)[encname_len] = '\0';
pec->pec_cn.cn_namelen = encname_len;
- Several possibilities for integer overflows in pefs_xbase64.c:
if (datalength + 1 > targsize)
return (-1);
// ...
target[tarindex] |= (pos - Base64) >> 4;
if ((size_t)tarindex + 1 < targsize)
target[tarindex+1] =
((pos - Base64) & 0x0f) << 4 ;
// ...
target[tarindex] |= (pos - Base64) >> 2;
if ((size_t)tarindex + 1 < targsize)
target[tarindex+1] =
((pos - Base64) & 0x03) << 6;
- Another in pefs_write_int() that could cause the
vnode_pager_setsize() call to be skipped, which might lead to
memory corruption down the road:
nsize = fsize;
MPASS(uio->uio_offset <= fsize);
if (uio->uio_offset + uio->uio_resid > nsize) {
PEFSDEBUG("pefs_write: extend: 0x%jx (old size: 0x%jx)\n",
uio->uio_offset + uio->uio_resid, nsize);
nsize = uio->uio_offset + uio->uio_resid;
vnode_pager_setsize(vp, nsize);
}
3. Commendations
PEFS does a lot of things right. It uses standard constructions like
XTS mode, PBKDF2, and HKDF. It also diligently zeroes buffers that
once contained sensitive information. This makes auditing easier.
Correction 12/05/2014: XTS mode is probably not the ideal option, see
Thomas Ptacek's blog post for good reasons why:
http://sockpuppet.org/blog/2014/04/30/you-dont-want-xts/
4. Recommendations
There are some things that PEFS could do better:
- Instead of re-implementing CBC mode in pefs_name_enccbc(), it
would be better to use a well-tested implementation from a crypto
library.
- Use test vectors in unit tests and runtime tests to make sure the
crypto algorithms (XTS, HKDF, PBKDF2, AES, SHA, etc.) are
correct.
- Make pefs_hkdf_expand() take a uint8_t instead of an int for the
'idx' parameter, so that there is a compiler warning when the
function is used incorrectly. Or check that 'idx' is between
0 and 255.
5. Future Work
5.1. Memory Corruption Vulnerabilities
This audit did not focus on "classic" memory corruptions
vulnerabilities. Because of the integer overflow issues documented in
Issue 2.9, I think PEFS could benefit from an audit that specifically
focuses on these concerns.
5.2. The Tweak's Triple Burden
The 64-bit random tweak is re-used for three different purposes:
1. It acts like an IV for the filename encryption.
2. It is part of the XTS mode tweak.
3. (After being Encrypted) The VMAC IV.
There was not enough time in the audit to determine whether any of
these three uses interact in negative ways that would, for example,
lead to at least one of filename encryption, file contents
encryption, or the VMAC being broken. More analysis is needed.
The VMAC's IV appears to be taken from its input, which probably
causes problems. This issue was not investigated in this audit since
the VMAC is only used as a non-cryptographic checksum, and should not
be relied on for security.
6. Conclusion
This audit found several issues, the most significant of which are
issues 2.3, 2.6, and 2.7. The possible existence of memory corruption
bugs (Issue 2.9) is worrisome as well, since this code runs in kernel
space.
PEFS would benefit from more auditing time.
7. References
[1] https://wiki.freebsd.org/PEFS
[2] https://github.com/glk/pefs
[3] http://www.iacr.org/archive/ches2009/57470001/57470001.pdf


