Skip to content

Commit 13c1bf0

Browse files
authored
Merge pull request libgit2#4197 from pks-t/pks/verify-object-hashes
Verify object hashes
2 parents d870284 + e0973bc commit 13c1bf0

File tree

7 files changed

+162
-25
lines changed

7 files changed

+162
-25
lines changed

include/git2/common.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ typedef enum {
182182
GIT_OPT_ENABLE_SYNCHRONOUS_OBJECT_CREATION,
183183
GIT_OPT_GET_WINDOWS_SHAREMODE,
184184
GIT_OPT_SET_WINDOWS_SHAREMODE,
185+
GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION,
185186
} git_libgit2_opt_t;
186187

187188
/**
@@ -337,6 +338,13 @@ typedef enum {
337338
* > is written to permanent storage, not simply cached. This
338339
* > defaults to disabled.
339340
*
341+
* opts(GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, int enabled)
342+
*
343+
* > Enable strict verification of object hashsums when reading
344+
* > objects from disk. This may impact performance due to an
345+
* > additional checksum calculation on each object. This defaults
346+
* > to enabled.
347+
*
340348
* @param option Option key
341349
* @param ... value to set the option
342350
* @return 0 on success, <0 on failure

include/git2/errors.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ typedef enum {
5454
GIT_PASSTHROUGH = -30, /**< Internal only */
5555
GIT_ITEROVER = -31, /**< Signals end of iteration with iterator */
5656
GIT_RETRY = -32, /**< Internal only */
57+
GIT_EMISMATCH = -33, /**< Hashsum mismatch in object */
5758
} git_error_code;
5859

5960
/**

src/odb.c

Lines changed: 66 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131

3232
#define GIT_ALTERNATES_MAX_DEPTH 5
3333

34+
bool git_odb__strict_hash_verification = true;
35+
3436
typedef struct
3537
{
3638
git_odb_backend *backend;
@@ -998,7 +1000,9 @@ static int odb_read_1(git_odb_object **out, git_odb *db, const git_oid *id,
9981000
size_t i;
9991001
git_rawobj raw;
10001002
git_odb_object *object;
1003+
git_oid hashed;
10011004
bool found = false;
1005+
int error;
10021006

10031007
if (!only_refreshed && odb_read_hardcoded(&raw, id) == 0)
10041008
found = true;
@@ -1011,7 +1015,7 @@ static int odb_read_1(git_odb_object **out, git_odb *db, const git_oid *id,
10111015
continue;
10121016

10131017
if (b->read != NULL) {
1014-
int error = b->read(&raw.data, &raw.len, &raw.type, b, id);
1018+
error = b->read(&raw.data, &raw.len, &raw.type, b, id);
10151019
if (error == GIT_PASSTHROUGH || error == GIT_ENOTFOUND)
10161020
continue;
10171021

@@ -1025,12 +1029,26 @@ static int odb_read_1(git_odb_object **out, git_odb *db, const git_oid *id,
10251029
if (!found)
10261030
return GIT_ENOTFOUND;
10271031

1032+
if (git_odb__strict_hash_verification) {
1033+
if ((error = git_odb_hash(&hashed, raw.data, raw.len, raw.type)) < 0)
1034+
goto out;
1035+
1036+
if (!git_oid_equal(id, &hashed)) {
1037+
error = git_odb__error_mismatch(id, &hashed);
1038+
goto out;
1039+
}
1040+
}
1041+
10281042
giterr_clear();
10291043
if ((object = odb_object__alloc(id, &raw)) == NULL)
1030-
return -1;
1044+
goto out;
10311045

10321046
*out = git_cache_store_raw(odb_cache(db), object);
1033-
return 0;
1047+
1048+
out:
1049+
if (error)
1050+
git__free(raw.data);
1051+
return error;
10341052
}
10351053

10361054
int git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id)
@@ -1081,9 +1099,9 @@ static int read_prefix_1(git_odb_object **out, git_odb *db,
10811099
const git_oid *key, size_t len, bool only_refreshed)
10821100
{
10831101
size_t i;
1084-
int error = GIT_ENOTFOUND;
1102+
int error;
10851103
git_oid found_full_oid = {{0}};
1086-
git_rawobj raw;
1104+
git_rawobj raw = {0};
10871105
void *data = NULL;
10881106
bool found = false;
10891107
git_odb_object *object;
@@ -1102,14 +1120,22 @@ static int read_prefix_1(git_odb_object **out, git_odb *db,
11021120
continue;
11031121

11041122
if (error)
1105-
return error;
1123+
goto out;
11061124

11071125
git__free(data);
11081126
data = raw.data;
11091127

11101128
if (found && git_oid__cmp(&full_oid, &found_full_oid)) {
1111-
git__free(raw.data);
1112-
return git_odb__error_ambiguous("multiple matches for prefix");
1129+
git_buf buf = GIT_BUF_INIT;
1130+
1131+
git_buf_printf(&buf, "multiple matches for prefix: %s",
1132+
git_oid_tostr_s(&full_oid));
1133+
git_buf_printf(&buf, " %s",
1134+
git_oid_tostr_s(&found_full_oid));
1135+
1136+
error = git_odb__error_ambiguous(buf.ptr);
1137+
git_buf_free(&buf);
1138+
goto out;
11131139
}
11141140

11151141
found_full_oid = full_oid;
@@ -1120,11 +1146,28 @@ static int read_prefix_1(git_odb_object **out, git_odb *db,
11201146
if (!found)
11211147
return GIT_ENOTFOUND;
11221148

1149+
if (git_odb__strict_hash_verification) {
1150+
git_oid hash;
1151+
1152+
if ((error = git_odb_hash(&hash, raw.data, raw.len, raw.type)) < 0)
1153+
goto out;
1154+
1155+
if (!git_oid_equal(&found_full_oid, &hash)) {
1156+
error = git_odb__error_mismatch(&found_full_oid, &hash);
1157+
goto out;
1158+
}
1159+
}
1160+
11231161
if ((object = odb_object__alloc(&found_full_oid, &raw)) == NULL)
1124-
return -1;
1162+
goto out;
11251163

11261164
*out = git_cache_store_raw(odb_cache(db), object);
1127-
return 0;
1165+
1166+
out:
1167+
if (error)
1168+
git__free(raw.data);
1169+
1170+
return error;
11281171
}
11291172

11301173
int git_odb_read_prefix(
@@ -1411,6 +1454,19 @@ int git_odb_refresh(struct git_odb *db)
14111454
return 0;
14121455
}
14131456

1457+
int git_odb__error_mismatch(const git_oid *expected, const git_oid *actual)
1458+
{
1459+
char expected_oid[GIT_OID_HEXSZ + 1], actual_oid[GIT_OID_HEXSZ + 1];
1460+
1461+
git_oid_tostr(expected_oid, sizeof(expected_oid), expected);
1462+
git_oid_tostr(actual_oid, sizeof(actual_oid), actual);
1463+
1464+
giterr_set(GITERR_ODB, "object hash mismatch - expected %s but got %s",
1465+
expected_oid, actual_oid);
1466+
1467+
return GIT_EMISMATCH;
1468+
}
1469+
14141470
int git_odb__error_notfound(
14151471
const char *message, const git_oid *oid, size_t oid_len)
14161472
{

src/odb.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
#define GIT_OBJECT_DIR_MODE 0777
2121
#define GIT_OBJECT_FILE_MODE 0444
2222

23+
extern bool git_odb__strict_hash_verification;
24+
2325
/* DO NOT EXPORT */
2426
typedef struct {
2527
void *data; /**< Raw, decompressed object data. */
@@ -96,6 +98,12 @@ int git_odb__hashfd_filtered(
9698
*/
9799
int git_odb__hashlink(git_oid *out, const char *path);
98100

101+
/**
102+
* Generate a GIT_EMISMATCH error for the ODB.
103+
*/
104+
int git_odb__error_mismatch(
105+
const git_oid *expected, const git_oid *actual);
106+
99107
/*
100108
* Generate a GIT_ENOTFOUND error for the ODB.
101109
*/

src/settings.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "cache.h"
1616
#include "global.h"
1717
#include "object.h"
18+
#include "odb.h"
1819
#include "refs.h"
1920
#include "transports/smart.h"
2021

@@ -243,6 +244,10 @@ int git_libgit2_opts(int key, ...)
243244
#endif
244245
break;
245246

247+
case GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION:
248+
git_odb__strict_hash_verification = (va_arg(ap, int) != 0);
249+
break;
250+
246251
default:
247252
giterr_set(GITERR_INVALID, "invalid option key");
248253
error = -1;

tests/object/lookup.c

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,12 @@ static git_repository *g_repo;
66

77
void test_object_lookup__initialize(void)
88
{
9-
cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git")));
9+
g_repo = cl_git_sandbox_init("testrepo.git");
1010
}
1111

1212
void test_object_lookup__cleanup(void)
1313
{
14-
git_repository_free(g_repo);
15-
g_repo = NULL;
14+
cl_git_sandbox_cleanup();
1615
}
1716

1817
void test_object_lookup__lookup_wrong_type_returns_enotfound(void)
@@ -63,3 +62,60 @@ void test_object_lookup__lookup_wrong_type_eventually_returns_enotfound(void)
6362
GIT_ENOTFOUND, git_object_lookup(&object, g_repo, &oid, GIT_OBJ_TAG));
6463
}
6564

65+
void test_object_lookup__lookup_corrupt_object_returns_error(void)
66+
{
67+
const char *commit = "8e73b769e97678d684b809b163bebdae2911720f",
68+
*file = "objects/8e/73b769e97678d684b809b163bebdae2911720f";
69+
git_buf path = GIT_BUF_INIT, contents = GIT_BUF_INIT;
70+
git_oid oid;
71+
git_object *object;
72+
size_t i;
73+
74+
cl_git_pass(git_oid_fromstr(&oid, commit));
75+
cl_git_pass(git_buf_joinpath(&path, git_repository_path(g_repo), file));
76+
cl_git_pass(git_futils_readbuffer(&contents, path.ptr));
77+
78+
/* Corrupt and try to read the object */
79+
for (i = 0; i < contents.size; i++) {
80+
contents.ptr[i] ^= 0x1;
81+
cl_git_pass(git_futils_writebuffer(&contents, path.ptr, O_RDWR, 0644));
82+
cl_git_fail(git_object_lookup(&object, g_repo, &oid, GIT_OBJ_COMMIT));
83+
contents.ptr[i] ^= 0x1;
84+
}
85+
86+
/* Restore original content and assert we can read the object */
87+
cl_git_pass(git_futils_writebuffer(&contents, path.ptr, O_RDWR, 0644));
88+
cl_git_pass(git_object_lookup(&object, g_repo, &oid, GIT_OBJ_COMMIT));
89+
90+
git_object_free(object);
91+
git_buf_free(&path);
92+
git_buf_free(&contents);
93+
}
94+
95+
void test_object_lookup__lookup_object_with_wrong_hash_returns_error(void)
96+
{
97+
const char *oldloose = "objects/8e/73b769e97678d684b809b163bebdae2911720f",
98+
*newloose = "objects/8e/73b769e97678d684b809b163bebdae2911720e",
99+
*commit = "8e73b769e97678d684b809b163bebdae2911720e";
100+
git_buf oldpath = GIT_BUF_INIT, newpath = GIT_BUF_INIT;
101+
git_object *object;
102+
git_oid oid;
103+
104+
cl_git_pass(git_oid_fromstr(&oid, commit));
105+
106+
/* Copy object to another location with wrong hash */
107+
cl_git_pass(git_buf_joinpath(&oldpath, git_repository_path(g_repo), oldloose));
108+
cl_git_pass(git_buf_joinpath(&newpath, git_repository_path(g_repo), newloose));
109+
cl_git_pass(git_futils_cp(oldpath.ptr, newpath.ptr, 0644));
110+
111+
/* Verify that lookup fails due to a hashsum mismatch */
112+
cl_git_fail_with(GIT_EMISMATCH, git_object_lookup(&object, g_repo, &oid, GIT_OBJ_COMMIT));
113+
114+
/* Disable verification and try again */
115+
cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, 0));
116+
cl_git_pass(git_object_lookup(&object, g_repo, &oid, GIT_OBJ_COMMIT));
117+
cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, 1));
118+
119+
git_buf_free(&oldpath);
120+
git_buf_free(&newpath);
121+
}

0 commit comments

Comments
 (0)