4358dcf4e2
When syncing ext4 with upstream commitf8f4acb6cd
("ext4: use generic casefolding support"), we forgot to take into account that some additional checks for the encryption key are needed for the encrypt+casefold support which isn't upstream yet. (These checks for the encryption key are still racy since they happen too late, but apparently they worked well enough...) This bug made it impossible to delete encrypted+casefolded directories without the encryption key, due to errors like: W : EXT4-fs warning (device vdc): __ext4fs_dirhash:270: inode #49202: comm Binder:378_4: Siphash requires key Fixes:76bfcb2dc2
("ANDROID: sync generic casefolding code with patches going upstream") Bug: 161184936 Bug: 172809853 Test: in kvm-xfstests test appliance: mkfs.ext4 -F -E encoding=utf8 -O encrypt /dev/vdc mount /vdc mkdir /vdc/dir chattr +F /vdc/dir keyid=$(head -c 64 /dev/zero | xfs_io -c add_enckey /vdc | awk '{print $NF}') xfs_io -c "set_encpolicy $keyid" /vdc/dir for i in `seq 1 100`; do mkdir /vdc/dir/$i done xfs_io -c "rm_enckey $keyid" /vdc rm -rf /vdc/dir # fails with the bug Change-Id: I67fbc9e34778519b229835e1d1b784bd42006ce7 Signed-off-by: Eric Biggers <ebiggers@google.com>
320 lines
7.3 KiB
C
320 lines
7.3 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* linux/fs/ext4/hash.c
|
|
*
|
|
* Copyright (C) 2002 by Theodore Ts'o
|
|
*/
|
|
|
|
#include <linux/fs.h>
|
|
#include <linux/unicode.h>
|
|
#include <linux/compiler.h>
|
|
#include <linux/bitops.h>
|
|
#include "ext4.h"
|
|
|
|
#define DELTA 0x9E3779B9
|
|
|
|
static void TEA_transform(__u32 buf[4], __u32 const in[])
|
|
{
|
|
__u32 sum = 0;
|
|
__u32 b0 = buf[0], b1 = buf[1];
|
|
__u32 a = in[0], b = in[1], c = in[2], d = in[3];
|
|
int n = 16;
|
|
|
|
do {
|
|
sum += DELTA;
|
|
b0 += ((b1 << 4)+a) ^ (b1+sum) ^ ((b1 >> 5)+b);
|
|
b1 += ((b0 << 4)+c) ^ (b0+sum) ^ ((b0 >> 5)+d);
|
|
} while (--n);
|
|
|
|
buf[0] += b0;
|
|
buf[1] += b1;
|
|
}
|
|
|
|
/* F, G and H are basic MD4 functions: selection, majority, parity */
|
|
#define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z))))
|
|
#define G(x, y, z) (((x) & (y)) + (((x) ^ (y)) & (z)))
|
|
#define H(x, y, z) ((x) ^ (y) ^ (z))
|
|
|
|
/*
|
|
* The generic round function. The application is so specific that
|
|
* we don't bother protecting all the arguments with parens, as is generally
|
|
* good macro practice, in favor of extra legibility.
|
|
* Rotation is separate from addition to prevent recomputation
|
|
*/
|
|
#define ROUND(f, a, b, c, d, x, s) \
|
|
(a += f(b, c, d) + x, a = rol32(a, s))
|
|
#define K1 0
|
|
#define K2 013240474631UL
|
|
#define K3 015666365641UL
|
|
|
|
/*
|
|
* Basic cut-down MD4 transform. Returns only 32 bits of result.
|
|
*/
|
|
static __u32 half_md4_transform(__u32 buf[4], __u32 const in[8])
|
|
{
|
|
__u32 a = buf[0], b = buf[1], c = buf[2], d = buf[3];
|
|
|
|
/* Round 1 */
|
|
ROUND(F, a, b, c, d, in[0] + K1, 3);
|
|
ROUND(F, d, a, b, c, in[1] + K1, 7);
|
|
ROUND(F, c, d, a, b, in[2] + K1, 11);
|
|
ROUND(F, b, c, d, a, in[3] + K1, 19);
|
|
ROUND(F, a, b, c, d, in[4] + K1, 3);
|
|
ROUND(F, d, a, b, c, in[5] + K1, 7);
|
|
ROUND(F, c, d, a, b, in[6] + K1, 11);
|
|
ROUND(F, b, c, d, a, in[7] + K1, 19);
|
|
|
|
/* Round 2 */
|
|
ROUND(G, a, b, c, d, in[1] + K2, 3);
|
|
ROUND(G, d, a, b, c, in[3] + K2, 5);
|
|
ROUND(G, c, d, a, b, in[5] + K2, 9);
|
|
ROUND(G, b, c, d, a, in[7] + K2, 13);
|
|
ROUND(G, a, b, c, d, in[0] + K2, 3);
|
|
ROUND(G, d, a, b, c, in[2] + K2, 5);
|
|
ROUND(G, c, d, a, b, in[4] + K2, 9);
|
|
ROUND(G, b, c, d, a, in[6] + K2, 13);
|
|
|
|
/* Round 3 */
|
|
ROUND(H, a, b, c, d, in[3] + K3, 3);
|
|
ROUND(H, d, a, b, c, in[7] + K3, 9);
|
|
ROUND(H, c, d, a, b, in[2] + K3, 11);
|
|
ROUND(H, b, c, d, a, in[6] + K3, 15);
|
|
ROUND(H, a, b, c, d, in[1] + K3, 3);
|
|
ROUND(H, d, a, b, c, in[5] + K3, 9);
|
|
ROUND(H, c, d, a, b, in[0] + K3, 11);
|
|
ROUND(H, b, c, d, a, in[4] + K3, 15);
|
|
|
|
buf[0] += a;
|
|
buf[1] += b;
|
|
buf[2] += c;
|
|
buf[3] += d;
|
|
|
|
return buf[1]; /* "most hashed" word */
|
|
}
|
|
#undef ROUND
|
|
#undef K1
|
|
#undef K2
|
|
#undef K3
|
|
#undef F
|
|
#undef G
|
|
#undef H
|
|
|
|
/* The old legacy hash */
|
|
static __u32 dx_hack_hash_unsigned(const char *name, int len)
|
|
{
|
|
__u32 hash, hash0 = 0x12a3fe2d, hash1 = 0x37abe8f9;
|
|
const unsigned char *ucp = (const unsigned char *) name;
|
|
|
|
while (len--) {
|
|
hash = hash1 + (hash0 ^ (((int) *ucp++) * 7152373));
|
|
|
|
if (hash & 0x80000000)
|
|
hash -= 0x7fffffff;
|
|
hash1 = hash0;
|
|
hash0 = hash;
|
|
}
|
|
return hash0 << 1;
|
|
}
|
|
|
|
static __u32 dx_hack_hash_signed(const char *name, int len)
|
|
{
|
|
__u32 hash, hash0 = 0x12a3fe2d, hash1 = 0x37abe8f9;
|
|
const signed char *scp = (const signed char *) name;
|
|
|
|
while (len--) {
|
|
hash = hash1 + (hash0 ^ (((int) *scp++) * 7152373));
|
|
|
|
if (hash & 0x80000000)
|
|
hash -= 0x7fffffff;
|
|
hash1 = hash0;
|
|
hash0 = hash;
|
|
}
|
|
return hash0 << 1;
|
|
}
|
|
|
|
static void str2hashbuf_signed(const char *msg, int len, __u32 *buf, int num)
|
|
{
|
|
__u32 pad, val;
|
|
int i;
|
|
const signed char *scp = (const signed char *) msg;
|
|
|
|
pad = (__u32)len | ((__u32)len << 8);
|
|
pad |= pad << 16;
|
|
|
|
val = pad;
|
|
if (len > num*4)
|
|
len = num * 4;
|
|
for (i = 0; i < len; i++) {
|
|
val = ((int) scp[i]) + (val << 8);
|
|
if ((i % 4) == 3) {
|
|
*buf++ = val;
|
|
val = pad;
|
|
num--;
|
|
}
|
|
}
|
|
if (--num >= 0)
|
|
*buf++ = val;
|
|
while (--num >= 0)
|
|
*buf++ = pad;
|
|
}
|
|
|
|
static void str2hashbuf_unsigned(const char *msg, int len, __u32 *buf, int num)
|
|
{
|
|
__u32 pad, val;
|
|
int i;
|
|
const unsigned char *ucp = (const unsigned char *) msg;
|
|
|
|
pad = (__u32)len | ((__u32)len << 8);
|
|
pad |= pad << 16;
|
|
|
|
val = pad;
|
|
if (len > num*4)
|
|
len = num * 4;
|
|
for (i = 0; i < len; i++) {
|
|
val = ((int) ucp[i]) + (val << 8);
|
|
if ((i % 4) == 3) {
|
|
*buf++ = val;
|
|
val = pad;
|
|
num--;
|
|
}
|
|
}
|
|
if (--num >= 0)
|
|
*buf++ = val;
|
|
while (--num >= 0)
|
|
*buf++ = pad;
|
|
}
|
|
|
|
/*
|
|
* Returns the hash of a filename. If len is 0 and name is NULL, then
|
|
* this function can be used to test whether or not a hash version is
|
|
* supported.
|
|
*
|
|
* The seed is an 4 longword (32 bits) "secret" which can be used to
|
|
* uniquify a hash. If the seed is all zero's, then some default seed
|
|
* may be used.
|
|
*
|
|
* A particular hash version specifies whether or not the seed is
|
|
* represented, and whether or not the returned hash is 32 bits or 64
|
|
* bits. 32 bit hashes will return 0 for the minor hash.
|
|
*/
|
|
static int __ext4fs_dirhash(const struct inode *dir, const char *name, int len,
|
|
struct dx_hash_info *hinfo)
|
|
{
|
|
__u32 hash;
|
|
__u32 minor_hash = 0;
|
|
const char *p;
|
|
int i;
|
|
__u32 in[8], buf[4];
|
|
void (*str2hashbuf)(const char *, int, __u32 *, int) =
|
|
str2hashbuf_signed;
|
|
|
|
/* Initialize the default seed for the hash checksum functions */
|
|
buf[0] = 0x67452301;
|
|
buf[1] = 0xefcdab89;
|
|
buf[2] = 0x98badcfe;
|
|
buf[3] = 0x10325476;
|
|
|
|
/* Check to see if the seed is all zero's */
|
|
if (hinfo->seed) {
|
|
for (i = 0; i < 4; i++) {
|
|
if (hinfo->seed[i]) {
|
|
memcpy(buf, hinfo->seed, sizeof(buf));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
switch (hinfo->hash_version) {
|
|
case DX_HASH_LEGACY_UNSIGNED:
|
|
hash = dx_hack_hash_unsigned(name, len);
|
|
break;
|
|
case DX_HASH_LEGACY:
|
|
hash = dx_hack_hash_signed(name, len);
|
|
break;
|
|
case DX_HASH_HALF_MD4_UNSIGNED:
|
|
str2hashbuf = str2hashbuf_unsigned;
|
|
fallthrough;
|
|
case DX_HASH_HALF_MD4:
|
|
p = name;
|
|
while (len > 0) {
|
|
(*str2hashbuf)(p, len, in, 8);
|
|
half_md4_transform(buf, in);
|
|
len -= 32;
|
|
p += 32;
|
|
}
|
|
minor_hash = buf[2];
|
|
hash = buf[1];
|
|
break;
|
|
case DX_HASH_TEA_UNSIGNED:
|
|
str2hashbuf = str2hashbuf_unsigned;
|
|
fallthrough;
|
|
case DX_HASH_TEA:
|
|
p = name;
|
|
while (len > 0) {
|
|
(*str2hashbuf)(p, len, in, 4);
|
|
TEA_transform(buf, in);
|
|
len -= 16;
|
|
p += 16;
|
|
}
|
|
hash = buf[0];
|
|
minor_hash = buf[1];
|
|
break;
|
|
case DX_HASH_SIPHASH:
|
|
{
|
|
struct qstr qname = QSTR_INIT(name, len);
|
|
__u64 combined_hash;
|
|
|
|
if (fscrypt_has_encryption_key(dir)) {
|
|
combined_hash = fscrypt_fname_siphash(dir, &qname);
|
|
} else {
|
|
ext4_warning_inode(dir, "Siphash requires key");
|
|
return -1;
|
|
}
|
|
|
|
hash = (__u32)(combined_hash >> 32);
|
|
minor_hash = (__u32)combined_hash;
|
|
break;
|
|
}
|
|
default:
|
|
hinfo->hash = 0;
|
|
return -1;
|
|
}
|
|
hash = hash & ~1;
|
|
if (hash == (EXT4_HTREE_EOF_32BIT << 1))
|
|
hash = (EXT4_HTREE_EOF_32BIT - 1) << 1;
|
|
hinfo->hash = hash;
|
|
hinfo->minor_hash = minor_hash;
|
|
return 0;
|
|
}
|
|
|
|
int ext4fs_dirhash(const struct inode *dir, const char *name, int len,
|
|
struct dx_hash_info *hinfo)
|
|
{
|
|
#ifdef CONFIG_UNICODE
|
|
const struct unicode_map *um = dir->i_sb->s_encoding;
|
|
int r, dlen;
|
|
unsigned char *buff;
|
|
struct qstr qstr = {.name = name, .len = len };
|
|
|
|
if (len && IS_CASEFOLDED(dir) && um &&
|
|
(!IS_ENCRYPTED(dir) || fscrypt_has_encryption_key(dir))) {
|
|
buff = kzalloc(sizeof(char) * PATH_MAX, GFP_KERNEL);
|
|
if (!buff)
|
|
return -ENOMEM;
|
|
|
|
dlen = utf8_casefold(um, &qstr, buff, PATH_MAX);
|
|
if (dlen < 0) {
|
|
kfree(buff);
|
|
goto opaque_seq;
|
|
}
|
|
|
|
r = __ext4fs_dirhash(dir, buff, dlen, hinfo);
|
|
|
|
kfree(buff);
|
|
return r;
|
|
}
|
|
opaque_seq:
|
|
#endif
|
|
return __ext4fs_dirhash(dir, name, len, hinfo);
|
|
}
|