Skip to content

Commit caf1742

Browse files
committed
Merge branch 'tb/unsafe-hash-cleanup'
The API around choosing to use unsafe variant of SHA-1 implementation has been updated in an attempt to make it harder to abuse. * tb/unsafe-hash-cleanup: hash.h: drop unsafe_ function variants csum-file: introduce hashfile_checkpoint_init() t/helper/test-hash.c: use unsafe_hash_algo() csum-file.c: use unsafe_hash_algo() hash.h: introduce `unsafe_hash_algo()` csum-file.c: extract algop from hashfile_checksum_valid() csum-file: store the hash algorithm as a struct field t/helper/test-tool: implement sha1-unsafe helper
2 parents 58b5801 + 04292c3 commit caf1742

File tree

12 files changed

+107
-70
lines changed

12 files changed

+107
-70
lines changed

builtin/fast-import.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1106,7 +1106,7 @@ static void stream_blob(uintmax_t len, struct object_id *oidout, uintmax_t mark)
11061106
|| (pack_size + PACK_SIZE_THRESHOLD + len) < pack_size)
11071107
cycle_packfile();
11081108

1109-
the_hash_algo->unsafe_init_fn(&checkpoint.ctx);
1109+
hashfile_checkpoint_init(pack_file, &checkpoint);
11101110
hashfile_checkpoint(pack_file, &checkpoint);
11111111
offset = checkpoint.offset;
11121112

bulk-checkin.c

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ static int deflate_blob_to_pack(struct bulk_checkin_packfile *state,
261261
git_hash_ctx ctx;
262262
unsigned char obuf[16384];
263263
unsigned header_len;
264-
struct hashfile_checkpoint checkpoint = {0};
264+
struct hashfile_checkpoint checkpoint;
265265
struct pack_idx_entry *idx = NULL;
266266

267267
seekback = lseek(fd, 0, SEEK_CUR);
@@ -272,12 +272,15 @@ static int deflate_blob_to_pack(struct bulk_checkin_packfile *state,
272272
OBJ_BLOB, size);
273273
the_hash_algo->init_fn(&ctx);
274274
the_hash_algo->update_fn(&ctx, obuf, header_len);
275-
the_hash_algo->unsafe_init_fn(&checkpoint.ctx);
276275

277276
/* Note: idx is non-NULL when we are writing */
278-
if ((flags & HASH_WRITE_OBJECT) != 0)
277+
if ((flags & HASH_WRITE_OBJECT) != 0) {
279278
CALLOC_ARRAY(idx, 1);
280279

280+
prepare_to_stream(state, flags);
281+
hashfile_checkpoint_init(state->f, &checkpoint);
282+
}
283+
281284
already_hashed_to = 0;
282285

283286
while (1) {

csum-file.c

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ void hashflush(struct hashfile *f)
5050

5151
if (offset) {
5252
if (!f->skip_hash)
53-
the_hash_algo->unsafe_update_fn(&f->ctx, f->buffer, offset);
53+
f->algop->update_fn(&f->ctx, f->buffer, offset);
5454
flush(f, f->buffer, offset);
5555
f->offset = 0;
5656
}
@@ -71,14 +71,14 @@ int finalize_hashfile(struct hashfile *f, unsigned char *result,
7171
hashflush(f);
7272

7373
if (f->skip_hash)
74-
hashclr(f->buffer, the_repository->hash_algo);
74+
hashclr(f->buffer, f->algop);
7575
else
76-
the_hash_algo->unsafe_final_fn(f->buffer, &f->ctx);
76+
f->algop->final_fn(f->buffer, &f->ctx);
7777

7878
if (result)
79-
hashcpy(result, f->buffer, the_repository->hash_algo);
79+
hashcpy(result, f->buffer, f->algop);
8080
if (flags & CSUM_HASH_IN_STREAM)
81-
flush(f, f->buffer, the_hash_algo->rawsz);
81+
flush(f, f->buffer, f->algop->rawsz);
8282
if (flags & CSUM_FSYNC)
8383
fsync_component_or_die(component, f->fd, f->name);
8484
if (flags & CSUM_CLOSE) {
@@ -128,7 +128,7 @@ void hashwrite(struct hashfile *f, const void *buf, unsigned int count)
128128
* f->offset is necessarily zero.
129129
*/
130130
if (!f->skip_hash)
131-
the_hash_algo->unsafe_update_fn(&f->ctx, buf, nr);
131+
f->algop->update_fn(&f->ctx, buf, nr);
132132
flush(f, buf, nr);
133133
} else {
134134
/*
@@ -174,7 +174,9 @@ static struct hashfile *hashfd_internal(int fd, const char *name,
174174
f->name = name;
175175
f->do_crc = 0;
176176
f->skip_hash = 0;
177-
the_hash_algo->unsafe_init_fn(&f->ctx);
177+
178+
f->algop = unsafe_hash_algo(the_hash_algo);
179+
f->algop->init_fn(&f->ctx);
178180

179181
f->buffer_len = buffer_len;
180182
f->buffer = xmalloc(buffer_len);
@@ -204,11 +206,18 @@ struct hashfile *hashfd_throughput(int fd, const char *name, struct progress *tp
204206
return hashfd_internal(fd, name, tp, 8 * 1024);
205207
}
206208

209+
void hashfile_checkpoint_init(struct hashfile *f,
210+
struct hashfile_checkpoint *checkpoint)
211+
{
212+
memset(checkpoint, 0, sizeof(*checkpoint));
213+
f->algop->init_fn(&checkpoint->ctx);
214+
}
215+
207216
void hashfile_checkpoint(struct hashfile *f, struct hashfile_checkpoint *checkpoint)
208217
{
209218
hashflush(f);
210219
checkpoint->offset = f->total;
211-
the_hash_algo->unsafe_clone_fn(&checkpoint->ctx, &f->ctx);
220+
f->algop->clone_fn(&checkpoint->ctx, &f->ctx);
212221
}
213222

214223
int hashfile_truncate(struct hashfile *f, struct hashfile_checkpoint *checkpoint)
@@ -219,7 +228,7 @@ int hashfile_truncate(struct hashfile *f, struct hashfile_checkpoint *checkpoint
219228
lseek(f->fd, offset, SEEK_SET) != offset)
220229
return -1;
221230
f->total = offset;
222-
the_hash_algo->unsafe_clone_fn(&f->ctx, &checkpoint->ctx);
231+
f->algop->clone_fn(&f->ctx, &checkpoint->ctx);
223232
f->offset = 0; /* hashflush() was called in checkpoint */
224233
return 0;
225234
}
@@ -240,14 +249,15 @@ int hashfile_checksum_valid(const unsigned char *data, size_t total_len)
240249
{
241250
unsigned char got[GIT_MAX_RAWSZ];
242251
git_hash_ctx ctx;
243-
size_t data_len = total_len - the_hash_algo->rawsz;
252+
const struct git_hash_algo *algop = unsafe_hash_algo(the_hash_algo);
253+
size_t data_len = total_len - algop->rawsz;
244254

245-
if (total_len < the_hash_algo->rawsz)
255+
if (total_len < algop->rawsz)
246256
return 0; /* say "too short"? */
247257

248-
the_hash_algo->unsafe_init_fn(&ctx);
249-
the_hash_algo->unsafe_update_fn(&ctx, data, data_len);
250-
the_hash_algo->unsafe_final_fn(got, &ctx);
258+
algop->init_fn(&ctx);
259+
algop->update_fn(&ctx, data, data_len);
260+
algop->final_fn(got, &ctx);
251261

252-
return hasheq(got, data + data_len, the_repository->hash_algo);
262+
return hasheq(got, data + data_len, algop);
253263
}

csum-file.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ struct hashfile {
2020
size_t buffer_len;
2121
unsigned char *buffer;
2222
unsigned char *check_buffer;
23+
const struct git_hash_algo *algop;
2324

2425
/**
2526
* If non-zero, skip_hash indicates that we should
@@ -35,6 +36,7 @@ struct hashfile_checkpoint {
3536
git_hash_ctx ctx;
3637
};
3738

39+
void hashfile_checkpoint_init(struct hashfile *, struct hashfile_checkpoint *);
3840
void hashfile_checkpoint(struct hashfile *, struct hashfile_checkpoint *);
3941
int hashfile_truncate(struct hashfile *, struct hashfile_checkpoint *);
4042

hash.h

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -282,21 +282,6 @@ struct git_hash_algo {
282282
/* The hash finalization function for object IDs. */
283283
git_hash_final_oid_fn final_oid_fn;
284284

285-
/* The non-cryptographic hash initialization function. */
286-
git_hash_init_fn unsafe_init_fn;
287-
288-
/* The non-cryptographic hash context cloning function. */
289-
git_hash_clone_fn unsafe_clone_fn;
290-
291-
/* The non-cryptographic hash update function. */
292-
git_hash_update_fn unsafe_update_fn;
293-
294-
/* The non-cryptographic hash finalization function. */
295-
git_hash_final_fn unsafe_final_fn;
296-
297-
/* The non-cryptographic hash finalization function. */
298-
git_hash_final_oid_fn unsafe_final_oid_fn;
299-
300285
/* The OID of the empty tree. */
301286
const struct object_id *empty_tree;
302287

@@ -305,6 +290,9 @@ struct git_hash_algo {
305290

306291
/* The all-zeros OID. */
307292
const struct object_id *null_oid;
293+
294+
/* The unsafe variant of this hash function, if one exists. */
295+
const struct git_hash_algo *unsafe;
308296
};
309297
extern const struct git_hash_algo hash_algos[GIT_HASH_NALGOS];
310298

@@ -320,9 +308,17 @@ int hash_algo_by_length(int len);
320308
/* Identical, except for a pointer to struct git_hash_algo. */
321309
static inline int hash_algo_by_ptr(const struct git_hash_algo *p)
322310
{
323-
return p - hash_algos;
311+
size_t i;
312+
for (i = 0; i < GIT_HASH_NALGOS; i++) {
313+
const struct git_hash_algo *algop = &hash_algos[i];
314+
if (p == algop)
315+
return i;
316+
}
317+
return GIT_HASH_UNKNOWN;
324318
}
325319

320+
const struct git_hash_algo *unsafe_hash_algo(const struct git_hash_algo *algop);
321+
326322
const struct object_id *null_oid(void);
327323

328324
static inline int hashcmp(const unsigned char *sha1, const unsigned char *sha2, const struct git_hash_algo *algop)

object-file.c

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,22 @@ static void git_hash_unknown_final_oid(struct object_id *oid UNUSED,
202202
BUG("trying to finalize unknown hash");
203203
}
204204

205+
static const struct git_hash_algo sha1_unsafe_algo = {
206+
.name = "sha1",
207+
.format_id = GIT_SHA1_FORMAT_ID,
208+
.rawsz = GIT_SHA1_RAWSZ,
209+
.hexsz = GIT_SHA1_HEXSZ,
210+
.blksz = GIT_SHA1_BLKSZ,
211+
.init_fn = git_hash_sha1_init_unsafe,
212+
.clone_fn = git_hash_sha1_clone_unsafe,
213+
.update_fn = git_hash_sha1_update_unsafe,
214+
.final_fn = git_hash_sha1_final_unsafe,
215+
.final_oid_fn = git_hash_sha1_final_oid_unsafe,
216+
.empty_tree = &empty_tree_oid,
217+
.empty_blob = &empty_blob_oid,
218+
.null_oid = &null_oid_sha1,
219+
};
220+
205221
const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = {
206222
{
207223
.name = NULL,
@@ -214,11 +230,6 @@ const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = {
214230
.update_fn = git_hash_unknown_update,
215231
.final_fn = git_hash_unknown_final,
216232
.final_oid_fn = git_hash_unknown_final_oid,
217-
.unsafe_init_fn = git_hash_unknown_init,
218-
.unsafe_clone_fn = git_hash_unknown_clone,
219-
.unsafe_update_fn = git_hash_unknown_update,
220-
.unsafe_final_fn = git_hash_unknown_final,
221-
.unsafe_final_oid_fn = git_hash_unknown_final_oid,
222233
.empty_tree = NULL,
223234
.empty_blob = NULL,
224235
.null_oid = NULL,
@@ -234,11 +245,7 @@ const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = {
234245
.update_fn = git_hash_sha1_update,
235246
.final_fn = git_hash_sha1_final,
236247
.final_oid_fn = git_hash_sha1_final_oid,
237-
.unsafe_init_fn = git_hash_sha1_init_unsafe,
238-
.unsafe_clone_fn = git_hash_sha1_clone_unsafe,
239-
.unsafe_update_fn = git_hash_sha1_update_unsafe,
240-
.unsafe_final_fn = git_hash_sha1_final_unsafe,
241-
.unsafe_final_oid_fn = git_hash_sha1_final_oid_unsafe,
248+
.unsafe = &sha1_unsafe_algo,
242249
.empty_tree = &empty_tree_oid,
243250
.empty_blob = &empty_blob_oid,
244251
.null_oid = &null_oid_sha1,
@@ -254,11 +261,6 @@ const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = {
254261
.update_fn = git_hash_sha256_update,
255262
.final_fn = git_hash_sha256_final,
256263
.final_oid_fn = git_hash_sha256_final_oid,
257-
.unsafe_init_fn = git_hash_sha256_init,
258-
.unsafe_clone_fn = git_hash_sha256_clone,
259-
.unsafe_update_fn = git_hash_sha256_update,
260-
.unsafe_final_fn = git_hash_sha256_final,
261-
.unsafe_final_oid_fn = git_hash_sha256_final_oid,
262264
.empty_tree = &empty_tree_oid_sha256,
263265
.empty_blob = &empty_blob_oid_sha256,
264266
.null_oid = &null_oid_sha256,
@@ -305,6 +307,15 @@ int hash_algo_by_length(int len)
305307
return GIT_HASH_UNKNOWN;
306308
}
307309

310+
const struct git_hash_algo *unsafe_hash_algo(const struct git_hash_algo *algop)
311+
{
312+
/* If we have a faster "unsafe" implementation, use that. */
313+
if (algop->unsafe)
314+
return algop->unsafe;
315+
/* Otherwise use the default one. */
316+
return algop;
317+
}
318+
308319
/*
309320
* This is meant to hold a *small* number of objects that you would
310321
* want repo_read_object_file() to be able to return, but yet you do not want

t/helper/test-hash.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
#include "test-tool.h"
22
#include "hex.h"
33

4-
int cmd_hash_impl(int ac, const char **av, int algo)
4+
int cmd_hash_impl(int ac, const char **av, int algo, int unsafe)
55
{
66
git_hash_ctx ctx;
77
unsigned char hash[GIT_MAX_HEXSZ];
88
unsigned bufsz = 8192;
99
int binary = 0;
1010
char *buffer;
1111
const struct git_hash_algo *algop = &hash_algos[algo];
12+
if (unsafe)
13+
algop = unsafe_hash_algo(algop);
1214

1315
if (ac == 2) {
1416
if (!strcmp(av[1], "-b"))

t/helper/test-sha1.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
int cmd__sha1(int ac, const char **av)
55
{
6-
return cmd_hash_impl(ac, av, GIT_HASH_SHA1);
6+
return cmd_hash_impl(ac, av, GIT_HASH_SHA1, 0);
77
}
88

99
int cmd__sha1_is_sha1dc(int argc UNUSED, const char **argv UNUSED)
@@ -13,3 +13,8 @@ int cmd__sha1_is_sha1dc(int argc UNUSED, const char **argv UNUSED)
1313
#endif
1414
return 1;
1515
}
16+
17+
int cmd__sha1_unsafe(int ac, const char **av)
18+
{
19+
return cmd_hash_impl(ac, av, GIT_HASH_SHA1, 1);
20+
}

t/helper/test-sha1.sh

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,31 @@
33
dd if=/dev/zero bs=1048576 count=100 2>/dev/null |
44
/usr/bin/time t/helper/test-tool sha1 >/dev/null
55

6+
dd if=/dev/zero bs=1048576 count=100 2>/dev/null |
7+
/usr/bin/time t/helper/test-tool sha1-unsafe >/dev/null
8+
69
while read expect cnt pfx
710
do
811
case "$expect" in '#'*) continue ;; esac
9-
actual=$(
10-
{
11-
test -z "$pfx" || echo "$pfx"
12-
dd if=/dev/zero bs=1048576 count=$cnt 2>/dev/null |
13-
perl -pe 'y/\000/g/'
14-
} | ./t/helper/test-tool sha1 $cnt
15-
)
16-
if test "$expect" = "$actual"
17-
then
18-
echo "OK: $expect $cnt $pfx"
19-
else
20-
echo >&2 "OOPS: $cnt"
21-
echo >&2 "expect: $expect"
22-
echo >&2 "actual: $actual"
23-
exit 1
24-
fi
12+
for sha1 in sha1 sha1-unsafe
13+
do
14+
actual=$(
15+
{
16+
test -z "$pfx" || echo "$pfx"
17+
dd if=/dev/zero bs=1048576 count=$cnt 2>/dev/null |
18+
perl -pe 'y/\000/g/'
19+
} | ./t/helper/test-tool $sha1 $cnt
20+
)
21+
if test "$expect" = "$actual"
22+
then
23+
echo "OK ($sha1): $expect $cnt $pfx"
24+
else
25+
echo >&2 "OOPS ($sha1): $cnt"
26+
echo >&2 "expect ($sha1): $expect"
27+
echo >&2 "actual ($sha1): $actual"
28+
exit 1
29+
fi
30+
done
2531
done <<EOF
2632
da39a3ee5e6b4b0d3255bfef95601890afd80709 0
2733
3f786850e387550fdab836ed7e6dc881de23001b 0 a

t/helper/test-sha256.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33

44
int cmd__sha256(int ac, const char **av)
55
{
6-
return cmd_hash_impl(ac, av, GIT_HASH_SHA256);
6+
return cmd_hash_impl(ac, av, GIT_HASH_SHA256, 0);
77
}

0 commit comments

Comments
 (0)