Browse Source

oidtree: avoid unaligned access to crit-bit tree

The flexible array member "k" of struct cb_node is used to store the key
of the crit-bit tree node.  It offers no alignment guarantees -- in fact
the current struct layout puts it one byte after a 4-byte aligned
address, i.e. guaranteed to be misaligned.

oidtree uses a struct object_id as cb_node key.  Since cf0983213c (hash:
add an algo member to struct object_id, 2021-04-26) it requires 4-byte
alignment.  The mismatch is reported by UndefinedBehaviorSanitizer at
runtime like this:

hash.h:277:2: runtime error: member access within misaligned address 0x00015000802d for type 'struct object_id', which requires 4 byte alignment
0x00015000802d: note: pointer points here
 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  00
             ^
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior hash.h:277:2 in

We can fix that by:

1. eliminating the alignment requirement of struct object_id,
2. providing the alignment in struct cb_node, or
3. avoiding the issue by only using memcpy to access "k".

Currently we only store one of two values in "algo" in struct object_id.
We could use a uint8_t for that instead and widen it only once we add
support for our twohundredth algorithm or so.  That would not only avoid
alignment issues, but also reduce the memory requirements for each
instance of struct object_id by ca. 9%.

Supporting keys with alignment requirements might be useful to spread
the use of crit-bit trees.  It can be achieved by using a wider type for
"k" (e.g. uintmax_t), using different types for the members "byte" and
"otherbits" (e.g. uint16_t or uint32_t for each), or by avoiding the use
of flexible arrays like khash.h does.

This patch implements the third option, though, because it has the least
potential for causing side-effects and we're close to the next release.
If one of the other options is implemented later as well to get their
additional benefits we can get rid of the extra copies introduced here.

Reported-by: Andrzej Hunt <andrzej@ahunt.org>
Signed-off-by: René Scharfe <l.s.r@web.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
maint
René Scharfe 3 years ago committed by Junio C Hamano
parent
commit
8bcda98da5
  1. 2
      cbtree.h
  2. 2
      hash.h
  3. 20
      oidtree.c

2
cbtree.h

@ -25,7 +25,7 @@ struct cb_node { @@ -25,7 +25,7 @@ struct cb_node {
*/
uint32_t byte;
uint8_t otherbits;
uint8_t k[FLEX_ARRAY]; /* arbitrary data */
uint8_t k[FLEX_ARRAY]; /* arbitrary data, unaligned */
};

struct cb_tree {

2
hash.h

@ -115,7 +115,7 @@ static inline void git_SHA256_Clone(git_SHA256_CTX *dst, const git_SHA256_CTX *s @@ -115,7 +115,7 @@ static inline void git_SHA256_Clone(git_SHA256_CTX *dst, const git_SHA256_CTX *s

struct object_id {
unsigned char hash[GIT_MAX_RAWSZ];
int algo;
int algo; /* XXX requires 4-byte alignment */
};

/* A suitably aligned type for stack allocations of hash contexts. */

20
oidtree.c

@ -31,12 +31,19 @@ void oidtree_clear(struct oidtree *ot) @@ -31,12 +31,19 @@ void oidtree_clear(struct oidtree *ot)
void oidtree_insert(struct oidtree *ot, const struct object_id *oid)
{
struct cb_node *on;
struct object_id k;

if (!oid->algo)
BUG("oidtree_insert requires oid->algo");

on = mem_pool_alloc(&ot->mem_pool, sizeof(*on) + sizeof(*oid));
oidcpy_with_padding((struct object_id *)on->k, oid);

/*
* Clear the padding and copy the result in separate steps to
* respect the 4-byte alignment needed by struct object_id.
*/
oidcpy_with_padding(&k, oid);
memcpy(on->k, &k, sizeof(k));

/*
* n.b. Current callers won't get us duplicates, here. If a
@ -68,17 +75,20 @@ int oidtree_contains(struct oidtree *ot, const struct object_id *oid) @@ -68,17 +75,20 @@ int oidtree_contains(struct oidtree *ot, const struct object_id *oid)
static enum cb_next iter(struct cb_node *n, void *arg)
{
struct oidtree_iter_data *x = arg;
const struct object_id *oid = (const struct object_id *)n->k;
struct object_id k;

/* Copy to provide 4-byte alignment needed by struct object_id. */
memcpy(&k, n->k, sizeof(k));

if (x->algo != GIT_HASH_UNKNOWN && x->algo != oid->algo)
if (x->algo != GIT_HASH_UNKNOWN && x->algo != k.algo)
return CB_CONTINUE;

if (x->last_nibble_at) {
if ((oid->hash[*x->last_nibble_at] ^ x->last_byte) & 0xf0)
if ((k.hash[*x->last_nibble_at] ^ x->last_byte) & 0xf0)
return CB_CONTINUE;
}

return x->fn(oid, x->arg);
return x->fn(&k, x->arg);
}

void oidtree_each(struct oidtree *ot, const struct object_id *oid,

Loading…
Cancel
Save