Merge branch 'tb/weak-sha1-for-tail-sum'

The checksum at the tail of files are now computed without
collision detection protection.  This is safe as the consumer of
the information to protect itself from replay attacks checks for
hash collisions independently.

* tb/weak-sha1-for-tail-sum:
  csum-file.c: use unsafe SHA-1 implementation when available
  Makefile: allow specifying a SHA-1 for non-cryptographic uses
  hash.h: scaffolding for _unsafe hashing variants
  sha1: do not redefine `platform_SHA_CTX` and friends
  pack-objects: use finalize_object_file() to rename pack/idx/etc
  finalize_object_file(): implement collision check
  finalize_object_file(): refactor unlink_or_warn() placement
  finalize_object_file(): check for name collision before renaming
This commit is contained in:
Junio C Hamano 2024-10-02 07:46:27 -07:00
commit ead0a050e2
11 changed files with 266 additions and 26 deletions

View File

@ -521,6 +521,10 @@ include shared.mak
# Define APPLE_COMMON_CRYPTO_SHA1 to use Apple's CommonCrypto for
# SHA-1.
#
# Define the same Makefile knobs as above, but suffixed with _UNSAFE to
# use the corresponding implementations for unsafe SHA-1 hashing for
# non-cryptographic purposes.
#
# If don't enable any of the *_SHA1 settings in this section, Git will
# default to its built-in sha1collisiondetection library, which is a
# collision-detecting sha1 This is slower, but may detect attempted
@ -1997,6 +2001,27 @@ endif
endif
endif
ifdef OPENSSL_SHA1_UNSAFE
ifndef OPENSSL_SHA1
EXTLIBS += $(LIB_4_CRYPTO)
BASIC_CFLAGS += -DSHA1_OPENSSL_UNSAFE
endif
else
ifdef BLK_SHA1_UNSAFE
ifndef BLK_SHA1
LIB_OBJS += block-sha1/sha1.o
BASIC_CFLAGS += -DSHA1_BLK_UNSAFE
endif
else
ifdef APPLE_COMMON_CRYPTO_SHA1_UNSAFE
ifndef APPLE_COMMON_CRYPTO_SHA1
COMPAT_CFLAGS += -DCOMMON_DIGEST_FOR_OPENSSL
BASIC_CFLAGS += -DSHA1_APPLE_UNSAFE
endif
endif
endif
endif
ifdef OPENSSL_SHA256
EXTLIBS += $(LIB_4_CRYPTO)
BASIC_CFLAGS += -DSHA256_OPENSSL

View File

@ -16,7 +16,9 @@ void blk_SHA1_Init(blk_SHA_CTX *ctx);
void blk_SHA1_Update(blk_SHA_CTX *ctx, const void *dataIn, size_t len);
void blk_SHA1_Final(unsigned char hashout[20], blk_SHA_CTX *ctx);
#ifndef platform_SHA_CTX
#define platform_SHA_CTX blk_SHA_CTX
#define platform_SHA1_Init blk_SHA1_Init
#define platform_SHA1_Update blk_SHA1_Update
#define platform_SHA1_Final blk_SHA1_Final
#endif

View File

@ -50,7 +50,7 @@ void hashflush(struct hashfile *f)
if (offset) {
if (!f->skip_hash)
the_hash_algo->update_fn(&f->ctx, f->buffer, offset);
the_hash_algo->unsafe_update_fn(&f->ctx, f->buffer, offset);
flush(f, f->buffer, offset);
f->offset = 0;
}
@ -73,7 +73,7 @@ int finalize_hashfile(struct hashfile *f, unsigned char *result,
if (f->skip_hash)
hashclr(f->buffer, the_repository->hash_algo);
else
the_hash_algo->final_fn(f->buffer, &f->ctx);
the_hash_algo->unsafe_final_fn(f->buffer, &f->ctx);
if (result)
hashcpy(result, f->buffer, the_repository->hash_algo);
@ -128,7 +128,7 @@ void hashwrite(struct hashfile *f, const void *buf, unsigned int count)
* f->offset is necessarily zero.
*/
if (!f->skip_hash)
the_hash_algo->update_fn(&f->ctx, buf, nr);
the_hash_algo->unsafe_update_fn(&f->ctx, buf, nr);
flush(f, buf, nr);
} else {
/*
@ -174,7 +174,7 @@ static struct hashfile *hashfd_internal(int fd, const char *name,
f->name = name;
f->do_crc = 0;
f->skip_hash = 0;
the_hash_algo->init_fn(&f->ctx);
the_hash_algo->unsafe_init_fn(&f->ctx);
f->buffer_len = buffer_len;
f->buffer = xmalloc(buffer_len);
@ -208,7 +208,7 @@ void hashfile_checkpoint(struct hashfile *f, struct hashfile_checkpoint *checkpo
{
hashflush(f);
checkpoint->offset = f->total;
the_hash_algo->clone_fn(&checkpoint->ctx, &f->ctx);
the_hash_algo->unsafe_clone_fn(&checkpoint->ctx, &f->ctx);
}
int hashfile_truncate(struct hashfile *f, struct hashfile_checkpoint *checkpoint)
@ -219,7 +219,7 @@ int hashfile_truncate(struct hashfile *f, struct hashfile_checkpoint *checkpoint
lseek(f->fd, offset, SEEK_SET) != offset)
return -1;
f->total = offset;
the_hash_algo->clone_fn(&f->ctx, &checkpoint->ctx);
the_hash_algo->unsafe_clone_fn(&f->ctx, &checkpoint->ctx);
f->offset = 0; /* hashflush() was called in checkpoint */
return 0;
}
@ -245,9 +245,9 @@ int hashfile_checksum_valid(const unsigned char *data, size_t total_len)
if (total_len < the_hash_algo->rawsz)
return 0; /* say "too short"? */
the_hash_algo->init_fn(&ctx);
the_hash_algo->update_fn(&ctx, data, data_len);
the_hash_algo->final_fn(got, &ctx);
the_hash_algo->unsafe_init_fn(&ctx);
the_hash_algo->unsafe_update_fn(&ctx, data, data_len);
the_hash_algo->unsafe_final_fn(got, &ctx);
return hasheq(got, data + data_len, the_repository->hash_algo);
}

72
hash.h
View File

@ -15,6 +15,36 @@
#include "block-sha1/sha1.h"
#endif
#if defined(SHA1_APPLE_UNSAFE)
# include <CommonCrypto/CommonDigest.h>
# define platform_SHA_CTX_unsafe CC_SHA1_CTX
# define platform_SHA1_Init_unsafe CC_SHA1_Init
# define platform_SHA1_Update_unsafe CC_SHA1_Update
# define platform_SHA1_Final_unsafe CC_SHA1_Final
#elif defined(SHA1_OPENSSL_UNSAFE)
# include <openssl/sha.h>
# if defined(OPENSSL_API_LEVEL) && OPENSSL_API_LEVEL >= 3
# define SHA1_NEEDS_CLONE_HELPER_UNSAFE
# include "sha1/openssl.h"
# define platform_SHA_CTX_unsafe openssl_SHA1_CTX
# define platform_SHA1_Init_unsafe openssl_SHA1_Init
# define platform_SHA1_Clone_unsafe openssl_SHA1_Clone
# define platform_SHA1_Update_unsafe openssl_SHA1_Update
# define platform_SHA1_Final_unsafe openssl_SHA1_Final
# else
# define platform_SHA_CTX_unsafe SHA_CTX
# define platform_SHA1_Init_unsafe SHA1_Init
# define platform_SHA1_Update_unsafe SHA1_Update
# define platform_SHA1_Final_unsafe SHA1_Final
# endif
#elif defined(SHA1_BLK_UNSAFE)
# include "block-sha1/sha1.h"
# define platform_SHA_CTX_unsafe blk_SHA_CTX
# define platform_SHA1_Init_unsafe blk_SHA1_Init
# define platform_SHA1_Update_unsafe blk_SHA1_Update
# define platform_SHA1_Final_unsafe blk_SHA1_Final
#endif
#if defined(SHA256_NETTLE)
#include "sha256/nettle.h"
#elif defined(SHA256_GCRYPT)
@ -44,14 +74,32 @@
#define platform_SHA1_Final SHA1_Final
#endif
#ifndef platform_SHA_CTX_unsafe
# define platform_SHA_CTX_unsafe platform_SHA_CTX
# define platform_SHA1_Init_unsafe platform_SHA1_Init
# define platform_SHA1_Update_unsafe platform_SHA1_Update
# define platform_SHA1_Final_unsafe platform_SHA1_Final
# ifdef platform_SHA1_Clone
# define platform_SHA1_Clone_unsafe platform_SHA1_Clone
# endif
#endif
#define git_SHA_CTX platform_SHA_CTX
#define git_SHA1_Init platform_SHA1_Init
#define git_SHA1_Update platform_SHA1_Update
#define git_SHA1_Final platform_SHA1_Final
#define git_SHA_CTX_unsafe platform_SHA_CTX_unsafe
#define git_SHA1_Init_unsafe platform_SHA1_Init_unsafe
#define git_SHA1_Update_unsafe platform_SHA1_Update_unsafe
#define git_SHA1_Final_unsafe platform_SHA1_Final_unsafe
#ifdef platform_SHA1_Clone
#define git_SHA1_Clone platform_SHA1_Clone
#endif
#ifdef platform_SHA1_Clone_unsafe
# define git_SHA1_Clone_unsafe platform_SHA1_Clone_unsafe
#endif
#ifndef platform_SHA256_CTX
#define platform_SHA256_CTX SHA256_CTX
@ -81,6 +129,13 @@ static inline void git_SHA1_Clone(git_SHA_CTX *dst, const git_SHA_CTX *src)
memcpy(dst, src, sizeof(*dst));
}
#endif
#ifndef SHA1_NEEDS_CLONE_HELPER_UNSAFE
static inline void git_SHA1_Clone_unsafe(git_SHA_CTX_unsafe *dst,
const git_SHA_CTX_unsafe *src)
{
memcpy(dst, src, sizeof(*dst));
}
#endif
#ifndef SHA256_NEEDS_CLONE_HELPER
static inline void git_SHA256_Clone(git_SHA256_CTX *dst, const git_SHA256_CTX *src)
@ -178,6 +233,8 @@ enum get_oid_result {
/* A suitably aligned type for stack allocations of hash contexts. */
union git_hash_ctx {
git_SHA_CTX sha1;
git_SHA_CTX_unsafe sha1_unsafe;
git_SHA256_CTX sha256;
};
typedef union git_hash_ctx git_hash_ctx;
@ -222,6 +279,21 @@ struct git_hash_algo {
/* The hash finalization function for object IDs. */
git_hash_final_oid_fn final_oid_fn;
/* The non-cryptographic hash initialization function. */
git_hash_init_fn unsafe_init_fn;
/* The non-cryptographic hash context cloning function. */
git_hash_clone_fn unsafe_clone_fn;
/* The non-cryptographic hash update function. */
git_hash_update_fn unsafe_update_fn;
/* The non-cryptographic hash finalization function. */
git_hash_final_fn unsafe_final_fn;
/* The non-cryptographic hash finalization function. */
git_hash_final_oid_fn unsafe_final_oid_fn;
/* The OID of the empty tree. */
const struct object_id *empty_tree;

View File

@ -115,6 +115,33 @@ static void git_hash_sha1_final_oid(struct object_id *oid, git_hash_ctx *ctx)
oid->algo = GIT_HASH_SHA1;
}
static void git_hash_sha1_init_unsafe(git_hash_ctx *ctx)
{
git_SHA1_Init_unsafe(&ctx->sha1_unsafe);
}
static void git_hash_sha1_clone_unsafe(git_hash_ctx *dst, const git_hash_ctx *src)
{
git_SHA1_Clone_unsafe(&dst->sha1_unsafe, &src->sha1_unsafe);
}
static void git_hash_sha1_update_unsafe(git_hash_ctx *ctx, const void *data,
size_t len)
{
git_SHA1_Update_unsafe(&ctx->sha1_unsafe, data, len);
}
static void git_hash_sha1_final_unsafe(unsigned char *hash, git_hash_ctx *ctx)
{
git_SHA1_Final_unsafe(hash, &ctx->sha1_unsafe);
}
static void git_hash_sha1_final_oid_unsafe(struct object_id *oid, git_hash_ctx *ctx)
{
git_SHA1_Final_unsafe(oid->hash, &ctx->sha1_unsafe);
memset(oid->hash + GIT_SHA1_RAWSZ, 0, GIT_MAX_RAWSZ - GIT_SHA1_RAWSZ);
oid->algo = GIT_HASH_SHA1;
}
static void git_hash_sha256_init(git_hash_ctx *ctx)
{
@ -189,6 +216,11 @@ const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = {
.update_fn = git_hash_unknown_update,
.final_fn = git_hash_unknown_final,
.final_oid_fn = git_hash_unknown_final_oid,
.unsafe_init_fn = git_hash_unknown_init,
.unsafe_clone_fn = git_hash_unknown_clone,
.unsafe_update_fn = git_hash_unknown_update,
.unsafe_final_fn = git_hash_unknown_final,
.unsafe_final_oid_fn = git_hash_unknown_final_oid,
.empty_tree = NULL,
.empty_blob = NULL,
.null_oid = NULL,
@ -204,6 +236,11 @@ const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = {
.update_fn = git_hash_sha1_update,
.final_fn = git_hash_sha1_final,
.final_oid_fn = git_hash_sha1_final_oid,
.unsafe_init_fn = git_hash_sha1_init_unsafe,
.unsafe_clone_fn = git_hash_sha1_clone_unsafe,
.unsafe_update_fn = git_hash_sha1_update_unsafe,
.unsafe_final_fn = git_hash_sha1_final_unsafe,
.unsafe_final_oid_fn = git_hash_sha1_final_oid_unsafe,
.empty_tree = &empty_tree_oid,
.empty_blob = &empty_blob_oid,
.null_oid = &null_oid_sha1,
@ -219,6 +256,11 @@ const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = {
.update_fn = git_hash_sha256_update,
.final_fn = git_hash_sha256_final,
.final_oid_fn = git_hash_sha256_final_oid,
.unsafe_init_fn = git_hash_sha256_init,
.unsafe_clone_fn = git_hash_sha256_clone,
.unsafe_update_fn = git_hash_sha256_update,
.unsafe_final_fn = git_hash_sha256_final,
.unsafe_final_oid_fn = git_hash_sha256_final_oid,
.empty_tree = &empty_tree_oid_sha256,
.empty_blob = &empty_blob_oid_sha256,
.null_oid = &null_oid_sha256,
@ -1932,17 +1974,77 @@ static void write_object_file_prepare_literally(const struct git_hash_algo *algo
hash_object_body(algo, &c, buf, len, oid, hdr, hdrlen);
}
static int check_collision(const char *filename_a, const char *filename_b)
{
char buf_a[4096], buf_b[4096];
int fd_a = -1, fd_b = -1;
int ret = 0;
fd_a = open(filename_a, O_RDONLY);
if (fd_a < 0) {
ret = error_errno(_("unable to open %s"), filename_a);
goto out;
}
fd_b = open(filename_b, O_RDONLY);
if (fd_b < 0) {
ret = error_errno(_("unable to open %s"), filename_b);
goto out;
}
while (1) {
ssize_t sz_a, sz_b;
sz_a = read_in_full(fd_a, buf_a, sizeof(buf_a));
if (sz_a < 0) {
ret = error_errno(_("unable to read %s"), filename_a);
goto out;
}
sz_b = read_in_full(fd_b, buf_b, sizeof(buf_b));
if (sz_b < 0) {
ret = error_errno(_("unable to read %s"), filename_b);
goto out;
}
if (sz_a != sz_b || memcmp(buf_a, buf_b, sz_a)) {
ret = error(_("files '%s' and '%s' differ in contents"),
filename_a, filename_b);
goto out;
}
if (sz_a < sizeof(buf_a))
break;
}
out:
if (fd_a > -1)
close(fd_a);
if (fd_b > -1)
close(fd_b);
return ret;
}
/*
* Move the just written object into its final resting place.
*/
int finalize_object_file(const char *tmpfile, const char *filename)
{
return finalize_object_file_flags(tmpfile, filename, 0);
}
int finalize_object_file_flags(const char *tmpfile, const char *filename,
enum finalize_object_file_flags flags)
{
struct stat st;
int ret = 0;
if (object_creation_mode == OBJECT_CREATION_USES_RENAMES)
goto try_rename;
else if (link(tmpfile, filename))
ret = errno;
else
unlink_or_warn(tmpfile);
/*
* Coda hack - coda doesn't like cross-directory links,
@ -1957,16 +2059,24 @@ int finalize_object_file(const char *tmpfile, const char *filename)
*/
if (ret && ret != EEXIST) {
try_rename:
if (!rename(tmpfile, filename))
if (!stat(filename, &st))
ret = EEXIST;
else if (!rename(tmpfile, filename))
goto out;
ret = errno;
else
ret = errno;
}
unlink_or_warn(tmpfile);
if (ret) {
if (ret != EEXIST) {
int saved_errno = errno;
unlink_or_warn(tmpfile);
errno = saved_errno;
return error_errno(_("unable to write file %s"), filename);
}
/* FIXME!!! Collision check here ? */
if (!(flags & FOF_SKIP_COLLISION_CHECK) &&
check_collision(tmpfile, filename))
return -1;
unlink_or_warn(tmpfile);
}
out:
@ -2219,7 +2329,8 @@ static int write_loose_object(const struct object_id *oid, char *hdr,
warning_errno(_("failed utime() on %s"), tmp_file.buf);
}
return finalize_object_file(tmp_file.buf, filename.buf);
return finalize_object_file_flags(tmp_file.buf, filename.buf,
FOF_SKIP_COLLISION_CHECK);
}
static int freshen_loose_object(const struct object_id *oid)
@ -2341,7 +2452,8 @@ int stream_loose_object(struct input_stream *in_stream, size_t len,
strbuf_release(&dir);
}
err = finalize_object_file(tmp_file.buf, filename.buf);
err = finalize_object_file_flags(tmp_file.buf, filename.buf,
FOF_SKIP_COLLISION_CHECK);
if (!err && compat)
err = repo_add_loose_object_map(the_repository, oid, &compat_oid);
cleanup:

View File

@ -117,7 +117,13 @@ int check_object_signature(struct repository *r, const struct object_id *oid,
*/
int stream_object_signature(struct repository *r, const struct object_id *oid);
enum finalize_object_file_flags {
FOF_SKIP_COLLISION_CHECK = 1,
};
int finalize_object_file(const char *tmpfile, const char *filename);
int finalize_object_file_flags(const char *tmpfile, const char *filename,
enum finalize_object_file_flags flags);
/* Helper to check and "touch" a file */
int check_and_freshen_file(const char *fn, int freshen);

View File

@ -8,6 +8,7 @@
#include "csum-file.h"
#include "remote.h"
#include "chunk-format.h"
#include "object-file.h"
#include "pack-mtimes.h"
#include "pack-objects.h"
#include "pack-revindex.h"
@ -528,9 +529,9 @@ static void rename_tmp_packfile(struct strbuf *name_prefix, const char *source,
size_t name_prefix_len = name_prefix->len;
strbuf_addstr(name_prefix, ext);
if (rename(source, name_prefix->buf))
die_errno("unable to rename temporary file to '%s'",
name_prefix->buf);
if (finalize_object_file(source, name_prefix->buf))
die("unable to rename temporary file to '%s'",
name_prefix->buf);
strbuf_setlen(name_prefix, name_prefix_len);
}

View File

@ -40,10 +40,12 @@ static inline void openssl_SHA1_Clone(struct openssl_SHA1_CTX *dst,
EVP_MD_CTX_copy_ex(dst->ectx, src->ectx);
}
#ifndef platform_SHA_CTX
#define platform_SHA_CTX openssl_SHA1_CTX
#define platform_SHA1_Init openssl_SHA1_Init
#define platform_SHA1_Clone openssl_SHA1_Clone
#define platform_SHA1_Update openssl_SHA1_Update
#define platform_SHA1_Final openssl_SHA1_Final
#endif
#endif /* SHA1_OPENSSL_H */

View File

@ -18,7 +18,10 @@ void git_SHA1DCFinal(unsigned char [20], SHA1_CTX *);
void git_SHA1DCUpdate(SHA1_CTX *ctx, const void *data, unsigned long len);
#define platform_SHA_IS_SHA1DC /* used by "test-tool sha1-is-sha1dc" */
#ifndef platform_SHA_CTX
#define platform_SHA_CTX SHA1_CTX
#define platform_SHA1_Init git_SHA1DCInit
#define platform_SHA1_Update git_SHA1DCUpdate
#define platform_SHA1_Final git_SHA1DCFinal
#endif

View File

@ -44,9 +44,14 @@ create_new_pack() {
}
do_repack() {
for f in $pack.*
do
mv $f "$(echo $f | sed -e 's/pack-/pack-corrupt-/')" || return 1
done &&
pack=$(printf "$blob_1\n$blob_2\n$blob_3\n" |
git pack-objects $@ .git/objects/pack/pack) &&
pack=".git/objects/pack/pack-${pack}"
pack=".git/objects/pack/pack-${pack}" &&
rm -f .git/objects/pack/pack-corrupt-*
}
do_corrupt_object() {

View File

@ -206,9 +206,11 @@ static int read_dir_paths(struct string_list *out, const char *path)
return 0;
}
static int migrate_paths(struct strbuf *src, struct strbuf *dst);
static int migrate_paths(struct strbuf *src, struct strbuf *dst,
enum finalize_object_file_flags flags);
static int migrate_one(struct strbuf *src, struct strbuf *dst)
static int migrate_one(struct strbuf *src, struct strbuf *dst,
enum finalize_object_file_flags flags)
{
struct stat st;
@ -220,12 +222,18 @@ static int migrate_one(struct strbuf *src, struct strbuf *dst)
return -1;
} else if (errno != EEXIST)
return -1;
return migrate_paths(src, dst);
return migrate_paths(src, dst, flags);
}
return finalize_object_file(src->buf, dst->buf);
return finalize_object_file_flags(src->buf, dst->buf, flags);
}
static int migrate_paths(struct strbuf *src, struct strbuf *dst)
static int is_loose_object_shard(const char *name)
{
return strlen(name) == 2 && isxdigit(name[0]) && isxdigit(name[1]);
}
static int migrate_paths(struct strbuf *src, struct strbuf *dst,
enum finalize_object_file_flags flags)
{
size_t src_len = src->len, dst_len = dst->len;
struct string_list paths = STRING_LIST_INIT_DUP;
@ -239,11 +247,15 @@ static int migrate_paths(struct strbuf *src, struct strbuf *dst)
for (i = 0; i < paths.nr; i++) {
const char *name = paths.items[i].string;
enum finalize_object_file_flags flags_copy = flags;
strbuf_addf(src, "/%s", name);
strbuf_addf(dst, "/%s", name);
ret |= migrate_one(src, dst);
if (is_loose_object_shard(name))
flags_copy |= FOF_SKIP_COLLISION_CHECK;
ret |= migrate_one(src, dst, flags_copy);
strbuf_setlen(src, src_len);
strbuf_setlen(dst, dst_len);
@ -271,7 +283,7 @@ int tmp_objdir_migrate(struct tmp_objdir *t)
strbuf_addbuf(&src, &t->path);
strbuf_addstr(&dst, repo_get_object_directory(the_repository));
ret = migrate_paths(&src, &dst);
ret = migrate_paths(&src, &dst, 0);
strbuf_release(&src);
strbuf_release(&dst);