Merge branch 'jk/hash-object-fsck'
"git hash-object" now checks that the resulting object is well formed with the same code as "git fsck". * jk/hash-object-fsck: fsck: do not assume NUL-termination of buffers hash-object: use fsck for object checks fsck: provide a function to fsck buffer without object struct t: use hash-object --literally when created malformed objects t7030: stop using invalid tag name t1006: stop using 0-padded timestamps t1007: modernize malformed object tests
This commit is contained in:
commit
abf2bb895b
94
fsck.c
94
fsck.c
@ -748,6 +748,23 @@ static int fsck_tree(const struct object_id *tree_oid,
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*
|
||||
* Confirm that the headers of a commit or tag object end in a reasonable way,
|
||||
* either with the usual "\n\n" separator, or at least with a trailing newline
|
||||
* on the final header line.
|
||||
*
|
||||
* This property is important for the memory safety of our callers. It allows
|
||||
* them to scan the buffer linewise without constantly checking the remaining
|
||||
* size as long as:
|
||||
*
|
||||
* - they check that there are bytes left in the buffer at the start of any
|
||||
* line (i.e., that the last newline they saw was not the final one we
|
||||
* found here)
|
||||
*
|
||||
* - any intra-line scanning they do will stop at a newline, which will worst
|
||||
* case hit the newline we found here as the end-of-header. This makes it
|
||||
* OK for them to use helpers like parse_oid_hex(), or even skip_prefix().
|
||||
*/
|
||||
static int verify_headers(const void *data, unsigned long size,
|
||||
const struct object_id *oid, enum object_type type,
|
||||
struct fsck_options *options)
|
||||
@ -808,6 +825,20 @@ static int fsck_ident(const char **ident,
|
||||
if (*p != ' ')
|
||||
return report(options, oid, type, FSCK_MSG_MISSING_SPACE_BEFORE_DATE, "invalid author/committer line - missing space before date");
|
||||
p++;
|
||||
/*
|
||||
* Our timestamp parser is based on the C strto*() functions, which
|
||||
* will happily eat whitespace, including the newline that is supposed
|
||||
* to prevent us walking past the end of the buffer. So do our own
|
||||
* scan, skipping linear whitespace but not newlines, and then
|
||||
* confirming we found a digit. We _could_ be even more strict here,
|
||||
* as we really expect only a single space, but since we have
|
||||
* traditionally allowed extra whitespace, we'll continue to do so.
|
||||
*/
|
||||
while (*p == ' ' || *p == '\t')
|
||||
p++;
|
||||
if (!isdigit(*p))
|
||||
return report(options, oid, type, FSCK_MSG_BAD_DATE,
|
||||
"invalid author/committer line - bad date");
|
||||
if (*p == '0' && p[1] != ' ')
|
||||
return report(options, oid, type, FSCK_MSG_ZERO_PADDED_DATE, "invalid author/committer line - zero-padded date");
|
||||
if (date_overflows(parse_timestamp(p, &end, 10)))
|
||||
@ -834,12 +865,18 @@ static int fsck_commit(const struct object_id *oid,
|
||||
unsigned author_count;
|
||||
int err;
|
||||
const char *buffer_begin = buffer;
|
||||
const char *buffer_end = buffer + size;
|
||||
const char *p;
|
||||
|
||||
/*
|
||||
* We _must_ stop parsing immediately if this reports failure, as the
|
||||
* memory safety of the rest of the function depends on it. See the
|
||||
* comment above the definition of verify_headers() for more details.
|
||||
*/
|
||||
if (verify_headers(buffer, size, oid, OBJ_COMMIT, options))
|
||||
return -1;
|
||||
|
||||
if (!skip_prefix(buffer, "tree ", &buffer))
|
||||
if (buffer >= buffer_end || !skip_prefix(buffer, "tree ", &buffer))
|
||||
return report(options, oid, OBJ_COMMIT, FSCK_MSG_MISSING_TREE, "invalid format - expected 'tree' line");
|
||||
if (parse_oid_hex(buffer, &tree_oid, &p) || *p != '\n') {
|
||||
err = report(options, oid, OBJ_COMMIT, FSCK_MSG_BAD_TREE_SHA1, "invalid 'tree' line format - bad sha1");
|
||||
@ -847,7 +884,7 @@ static int fsck_commit(const struct object_id *oid,
|
||||
return err;
|
||||
}
|
||||
buffer = p + 1;
|
||||
while (skip_prefix(buffer, "parent ", &buffer)) {
|
||||
while (buffer < buffer_end && skip_prefix(buffer, "parent ", &buffer)) {
|
||||
if (parse_oid_hex(buffer, &parent_oid, &p) || *p != '\n') {
|
||||
err = report(options, oid, OBJ_COMMIT, FSCK_MSG_BAD_PARENT_SHA1, "invalid 'parent' line format - bad sha1");
|
||||
if (err)
|
||||
@ -856,7 +893,7 @@ static int fsck_commit(const struct object_id *oid,
|
||||
buffer = p + 1;
|
||||
}
|
||||
author_count = 0;
|
||||
while (skip_prefix(buffer, "author ", &buffer)) {
|
||||
while (buffer < buffer_end && skip_prefix(buffer, "author ", &buffer)) {
|
||||
author_count++;
|
||||
err = fsck_ident(&buffer, oid, OBJ_COMMIT, options);
|
||||
if (err)
|
||||
@ -868,7 +905,7 @@ static int fsck_commit(const struct object_id *oid,
|
||||
err = report(options, oid, OBJ_COMMIT, FSCK_MSG_MULTIPLE_AUTHORS, "invalid format - multiple 'author' lines");
|
||||
if (err)
|
||||
return err;
|
||||
if (!skip_prefix(buffer, "committer ", &buffer))
|
||||
if (buffer >= buffer_end || !skip_prefix(buffer, "committer ", &buffer))
|
||||
return report(options, oid, OBJ_COMMIT, FSCK_MSG_MISSING_COMMITTER, "invalid format - expected 'committer' line");
|
||||
err = fsck_ident(&buffer, oid, OBJ_COMMIT, options);
|
||||
if (err)
|
||||
@ -899,13 +936,19 @@ int fsck_tag_standalone(const struct object_id *oid, const char *buffer,
|
||||
int ret = 0;
|
||||
char *eol;
|
||||
struct strbuf sb = STRBUF_INIT;
|
||||
const char *buffer_end = buffer + size;
|
||||
const char *p;
|
||||
|
||||
/*
|
||||
* We _must_ stop parsing immediately if this reports failure, as the
|
||||
* memory safety of the rest of the function depends on it. See the
|
||||
* comment above the definition of verify_headers() for more details.
|
||||
*/
|
||||
ret = verify_headers(buffer, size, oid, OBJ_TAG, options);
|
||||
if (ret)
|
||||
goto done;
|
||||
|
||||
if (!skip_prefix(buffer, "object ", &buffer)) {
|
||||
if (buffer >= buffer_end || !skip_prefix(buffer, "object ", &buffer)) {
|
||||
ret = report(options, oid, OBJ_TAG, FSCK_MSG_MISSING_OBJECT, "invalid format - expected 'object' line");
|
||||
goto done;
|
||||
}
|
||||
@ -916,11 +959,11 @@ int fsck_tag_standalone(const struct object_id *oid, const char *buffer,
|
||||
}
|
||||
buffer = p + 1;
|
||||
|
||||
if (!skip_prefix(buffer, "type ", &buffer)) {
|
||||
if (buffer >= buffer_end || !skip_prefix(buffer, "type ", &buffer)) {
|
||||
ret = report(options, oid, OBJ_TAG, FSCK_MSG_MISSING_TYPE_ENTRY, "invalid format - expected 'type' line");
|
||||
goto done;
|
||||
}
|
||||
eol = strchr(buffer, '\n');
|
||||
eol = memchr(buffer, '\n', buffer_end - buffer);
|
||||
if (!eol) {
|
||||
ret = report(options, oid, OBJ_TAG, FSCK_MSG_MISSING_TYPE, "invalid format - unexpected end after 'type' line");
|
||||
goto done;
|
||||
@ -932,11 +975,11 @@ int fsck_tag_standalone(const struct object_id *oid, const char *buffer,
|
||||
goto done;
|
||||
buffer = eol + 1;
|
||||
|
||||
if (!skip_prefix(buffer, "tag ", &buffer)) {
|
||||
if (buffer >= buffer_end || !skip_prefix(buffer, "tag ", &buffer)) {
|
||||
ret = report(options, oid, OBJ_TAG, FSCK_MSG_MISSING_TAG_ENTRY, "invalid format - expected 'tag' line");
|
||||
goto done;
|
||||
}
|
||||
eol = strchr(buffer, '\n');
|
||||
eol = memchr(buffer, '\n', buffer_end - buffer);
|
||||
if (!eol) {
|
||||
ret = report(options, oid, OBJ_TAG, FSCK_MSG_MISSING_TAG, "invalid format - unexpected end after 'type' line");
|
||||
goto done;
|
||||
@ -952,7 +995,7 @@ int fsck_tag_standalone(const struct object_id *oid, const char *buffer,
|
||||
}
|
||||
buffer = eol + 1;
|
||||
|
||||
if (!skip_prefix(buffer, "tagger ", &buffer)) {
|
||||
if (buffer >= buffer_end || !skip_prefix(buffer, "tagger ", &buffer)) {
|
||||
/* early tags do not contain 'tagger' lines; warn only */
|
||||
ret = report(options, oid, OBJ_TAG, FSCK_MSG_MISSING_TAGGER_ENTRY, "invalid format - expected 'tagger' line");
|
||||
if (ret)
|
||||
@ -960,10 +1003,8 @@ int fsck_tag_standalone(const struct object_id *oid, const char *buffer,
|
||||
}
|
||||
else
|
||||
ret = fsck_ident(&buffer, oid, OBJ_TAG, options);
|
||||
if (!*buffer)
|
||||
goto done;
|
||||
|
||||
if (!starts_with(buffer, "\n")) {
|
||||
if (buffer < buffer_end && !starts_with(buffer, "\n")) {
|
||||
/*
|
||||
* The verify_headers() check will allow
|
||||
* e.g. "[...]tagger <tagger>\nsome
|
||||
@ -1237,19 +1278,26 @@ int fsck_object(struct object *obj, void *data, unsigned long size,
|
||||
if (!obj)
|
||||
return report(options, NULL, OBJ_NONE, FSCK_MSG_BAD_OBJECT_SHA1, "no valid object to fsck");
|
||||
|
||||
if (obj->type == OBJ_BLOB)
|
||||
return fsck_blob(&obj->oid, data, size, options);
|
||||
if (obj->type == OBJ_TREE)
|
||||
return fsck_tree(&obj->oid, data, size, options);
|
||||
if (obj->type == OBJ_COMMIT)
|
||||
return fsck_commit(&obj->oid, data, size, options);
|
||||
if (obj->type == OBJ_TAG)
|
||||
return fsck_tag(&obj->oid, data, size, options);
|
||||
return fsck_buffer(&obj->oid, obj->type, data, size, options);
|
||||
}
|
||||
|
||||
return report(options, &obj->oid, obj->type,
|
||||
int fsck_buffer(const struct object_id *oid, enum object_type type,
|
||||
void *data, unsigned long size,
|
||||
struct fsck_options *options)
|
||||
{
|
||||
if (type == OBJ_BLOB)
|
||||
return fsck_blob(oid, data, size, options);
|
||||
if (type == OBJ_TREE)
|
||||
return fsck_tree(oid, data, size, options);
|
||||
if (type == OBJ_COMMIT)
|
||||
return fsck_commit(oid, data, size, options);
|
||||
if (type == OBJ_TAG)
|
||||
return fsck_tag(oid, data, size, options);
|
||||
|
||||
return report(options, oid, type,
|
||||
FSCK_MSG_UNKNOWN_TYPE,
|
||||
"unknown type '%d' (internal fsck error)",
|
||||
obj->type);
|
||||
type);
|
||||
}
|
||||
|
||||
int fsck_error_function(struct fsck_options *o,
|
||||
|
8
fsck.h
8
fsck.h
@ -183,6 +183,14 @@ int fsck_walk(struct object *obj, void *data, struct fsck_options *options);
|
||||
int fsck_object(struct object *obj, void *data, unsigned long size,
|
||||
struct fsck_options *options);
|
||||
|
||||
/*
|
||||
* Same as fsck_object(), but for when the caller doesn't have an object
|
||||
* struct.
|
||||
*/
|
||||
int fsck_buffer(const struct object_id *oid, enum object_type,
|
||||
void *data, unsigned long size,
|
||||
struct fsck_options *options);
|
||||
|
||||
/*
|
||||
* fsck a tag, and pass info about it back to the caller. This is
|
||||
* exposed fsck_object() internals for git-mktag(1).
|
||||
|
@ -33,6 +33,7 @@
|
||||
#include "object-store.h"
|
||||
#include "promisor-remote.h"
|
||||
#include "submodule.h"
|
||||
#include "fsck.h"
|
||||
|
||||
/* The maximum size for an object header. */
|
||||
#define MAX_HEADER_LEN 32
|
||||
@ -2284,32 +2285,21 @@ int repo_has_object_file(struct repository *r,
|
||||
return repo_has_object_file_with_flags(r, oid, 0);
|
||||
}
|
||||
|
||||
static void check_tree(const void *buf, size_t size)
|
||||
/*
|
||||
* We can't use the normal fsck_error_function() for index_mem(),
|
||||
* because we don't yet have a valid oid for it to report. Instead,
|
||||
* report the minimal fsck error here, and rely on the caller to
|
||||
* give more context.
|
||||
*/
|
||||
static int hash_format_check_report(struct fsck_options *opts,
|
||||
const struct object_id *oid,
|
||||
enum object_type object_type,
|
||||
enum fsck_msg_type msg_type,
|
||||
enum fsck_msg_id msg_id,
|
||||
const char *message)
|
||||
{
|
||||
struct tree_desc desc;
|
||||
struct name_entry entry;
|
||||
|
||||
init_tree_desc(&desc, buf, size);
|
||||
while (tree_entry(&desc, &entry))
|
||||
/* do nothing
|
||||
* tree_entry() will die() on malformed entries */
|
||||
;
|
||||
}
|
||||
|
||||
static void check_commit(const void *buf, size_t size)
|
||||
{
|
||||
struct commit c;
|
||||
memset(&c, 0, sizeof(c));
|
||||
if (parse_commit_buffer(the_repository, &c, buf, size, 0))
|
||||
die(_("corrupt commit"));
|
||||
}
|
||||
|
||||
static void check_tag(const void *buf, size_t size)
|
||||
{
|
||||
struct tag t;
|
||||
memset(&t, 0, sizeof(t));
|
||||
if (parse_tag_buffer(the_repository, &t, buf, size))
|
||||
die(_("corrupt tag"));
|
||||
error(_("object fails fsck: %s"), message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int index_mem(struct index_state *istate,
|
||||
@ -2336,12 +2326,13 @@ static int index_mem(struct index_state *istate,
|
||||
}
|
||||
}
|
||||
if (flags & HASH_FORMAT_CHECK) {
|
||||
if (type == OBJ_TREE)
|
||||
check_tree(buf, size);
|
||||
if (type == OBJ_COMMIT)
|
||||
check_commit(buf, size);
|
||||
if (type == OBJ_TAG)
|
||||
check_tag(buf, size);
|
||||
struct fsck_options opts = FSCK_OPTIONS_DEFAULT;
|
||||
|
||||
opts.strict = 1;
|
||||
opts.error_func = hash_format_check_report;
|
||||
if (fsck_buffer(null_oid(), type, buf, size, &opts))
|
||||
die(_("refusing to create malformed object"));
|
||||
fsck_finish(&opts);
|
||||
}
|
||||
|
||||
if (write_object)
|
||||
|
@ -292,8 +292,8 @@ commit_message="Initial commit"
|
||||
commit_sha1=$(echo_without_newline "$commit_message" | git commit-tree $tree_sha1)
|
||||
commit_size=$(($(test_oid hexsz) + 137))
|
||||
commit_content="tree $tree_sha1
|
||||
author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> 0000000000 +0000
|
||||
committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 0000000000 +0000
|
||||
author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> 0 +0000
|
||||
committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 0 +0000
|
||||
|
||||
$commit_message"
|
||||
|
||||
@ -304,7 +304,7 @@ type blob
|
||||
tag hellotag
|
||||
tagger $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>"
|
||||
tag_description="This is a tag"
|
||||
tag_content="$tag_header_without_timestamp 0000000000 +0000
|
||||
tag_content="$tag_header_without_timestamp 0 +0000
|
||||
|
||||
$tag_description"
|
||||
|
||||
|
@ -203,23 +203,34 @@ done
|
||||
test_expect_success 'too-short tree' '
|
||||
echo abc >malformed-tree &&
|
||||
test_must_fail git hash-object -t tree malformed-tree 2>err &&
|
||||
test_i18ngrep "too-short tree object" err
|
||||
grep "too-short tree object" err
|
||||
'
|
||||
|
||||
test_expect_success 'malformed mode in tree' '
|
||||
hex_sha1=$(echo foo | git hash-object --stdin -w) &&
|
||||
bin_sha1=$(echo $hex_sha1 | hex2oct) &&
|
||||
printf "9100644 \0$bin_sha1" >tree-with-malformed-mode &&
|
||||
hex_oid=$(echo foo | git hash-object --stdin -w) &&
|
||||
bin_oid=$(echo $hex_oid | hex2oct) &&
|
||||
printf "9100644 \0$bin_oid" >tree-with-malformed-mode &&
|
||||
test_must_fail git hash-object -t tree tree-with-malformed-mode 2>err &&
|
||||
test_i18ngrep "malformed mode in tree entry" err
|
||||
grep "malformed mode in tree entry" err
|
||||
'
|
||||
|
||||
test_expect_success 'empty filename in tree' '
|
||||
hex_sha1=$(echo foo | git hash-object --stdin -w) &&
|
||||
bin_sha1=$(echo $hex_sha1 | hex2oct) &&
|
||||
printf "100644 \0$bin_sha1" >tree-with-empty-filename &&
|
||||
hex_oid=$(echo foo | git hash-object --stdin -w) &&
|
||||
bin_oid=$(echo $hex_oid | hex2oct) &&
|
||||
printf "100644 \0$bin_oid" >tree-with-empty-filename &&
|
||||
test_must_fail git hash-object -t tree tree-with-empty-filename 2>err &&
|
||||
test_i18ngrep "empty filename in tree entry" err
|
||||
grep "empty filename in tree entry" err
|
||||
'
|
||||
|
||||
test_expect_success 'duplicate filename in tree' '
|
||||
hex_oid=$(echo foo | git hash-object --stdin -w) &&
|
||||
bin_oid=$(echo $hex_oid | hex2oct) &&
|
||||
{
|
||||
printf "100644 file\0$bin_oid" &&
|
||||
printf "100644 file\0$bin_oid"
|
||||
} >tree-with-duplicate-filename &&
|
||||
test_must_fail git hash-object -t tree tree-with-duplicate-filename 2>err &&
|
||||
grep "duplicateEntries" err
|
||||
'
|
||||
|
||||
test_expect_success 'corrupt commit' '
|
||||
|
@ -212,7 +212,7 @@ test_expect_success 'email without @ is okay' '
|
||||
test_expect_success 'email with embedded > is not okay' '
|
||||
git cat-file commit HEAD >basis &&
|
||||
sed "s/@[a-z]/&>/" basis >bad-email &&
|
||||
new=$(git hash-object -t commit -w --stdin <bad-email) &&
|
||||
new=$(git hash-object --literally -t commit -w --stdin <bad-email) &&
|
||||
test_when_finished "remove_object $new" &&
|
||||
git update-ref refs/heads/bogus "$new" &&
|
||||
test_when_finished "git update-ref -d refs/heads/bogus" &&
|
||||
@ -223,7 +223,7 @@ test_expect_success 'email with embedded > is not okay' '
|
||||
test_expect_success 'missing < email delimiter is reported nicely' '
|
||||
git cat-file commit HEAD >basis &&
|
||||
sed "s/<//" basis >bad-email-2 &&
|
||||
new=$(git hash-object -t commit -w --stdin <bad-email-2) &&
|
||||
new=$(git hash-object --literally -t commit -w --stdin <bad-email-2) &&
|
||||
test_when_finished "remove_object $new" &&
|
||||
git update-ref refs/heads/bogus "$new" &&
|
||||
test_when_finished "git update-ref -d refs/heads/bogus" &&
|
||||
@ -234,7 +234,7 @@ test_expect_success 'missing < email delimiter is reported nicely' '
|
||||
test_expect_success 'missing email is reported nicely' '
|
||||
git cat-file commit HEAD >basis &&
|
||||
sed "s/[a-z]* <[^>]*>//" basis >bad-email-3 &&
|
||||
new=$(git hash-object -t commit -w --stdin <bad-email-3) &&
|
||||
new=$(git hash-object --literally -t commit -w --stdin <bad-email-3) &&
|
||||
test_when_finished "remove_object $new" &&
|
||||
git update-ref refs/heads/bogus "$new" &&
|
||||
test_when_finished "git update-ref -d refs/heads/bogus" &&
|
||||
@ -245,7 +245,7 @@ test_expect_success 'missing email is reported nicely' '
|
||||
test_expect_success '> in name is reported' '
|
||||
git cat-file commit HEAD >basis &&
|
||||
sed "s/ </> </" basis >bad-email-4 &&
|
||||
new=$(git hash-object -t commit -w --stdin <bad-email-4) &&
|
||||
new=$(git hash-object --literally -t commit -w --stdin <bad-email-4) &&
|
||||
test_when_finished "remove_object $new" &&
|
||||
git update-ref refs/heads/bogus "$new" &&
|
||||
test_when_finished "git update-ref -d refs/heads/bogus" &&
|
||||
@ -258,7 +258,7 @@ test_expect_success 'integer overflow in timestamps is reported' '
|
||||
git cat-file commit HEAD >basis &&
|
||||
sed "s/^\\(author .*>\\) [0-9]*/\\1 18446744073709551617/" \
|
||||
<basis >bad-timestamp &&
|
||||
new=$(git hash-object -t commit -w --stdin <bad-timestamp) &&
|
||||
new=$(git hash-object --literally -t commit -w --stdin <bad-timestamp) &&
|
||||
test_when_finished "remove_object $new" &&
|
||||
git update-ref refs/heads/bogus "$new" &&
|
||||
test_when_finished "git update-ref -d refs/heads/bogus" &&
|
||||
@ -269,7 +269,7 @@ test_expect_success 'integer overflow in timestamps is reported' '
|
||||
test_expect_success 'commit with NUL in header' '
|
||||
git cat-file commit HEAD >basis &&
|
||||
sed "s/author ./author Q/" <basis | q_to_nul >commit-NUL-header &&
|
||||
new=$(git hash-object -t commit -w --stdin <commit-NUL-header) &&
|
||||
new=$(git hash-object --literally -t commit -w --stdin <commit-NUL-header) &&
|
||||
test_when_finished "remove_object $new" &&
|
||||
git update-ref refs/heads/bogus "$new" &&
|
||||
test_when_finished "git update-ref -d refs/heads/bogus" &&
|
||||
@ -292,7 +292,7 @@ test_expect_success 'tree object with duplicate entries' '
|
||||
git cat-file tree $T &&
|
||||
git cat-file tree $T
|
||||
) |
|
||||
git hash-object -w -t tree --stdin
|
||||
git hash-object --literally -w -t tree --stdin
|
||||
) &&
|
||||
test_must_fail git fsck 2>out &&
|
||||
test_i18ngrep "error in tree .*contains duplicate file entries" out
|
||||
@ -426,7 +426,7 @@ test_expect_success 'tag with incorrect tag name & missing tagger' '
|
||||
This is an invalid tag.
|
||||
EOF
|
||||
|
||||
tag=$(git hash-object -t tag -w --stdin <wrong-tag) &&
|
||||
tag=$(git hash-object --literally -t tag -w --stdin <wrong-tag) &&
|
||||
test_when_finished "remove_object $tag" &&
|
||||
echo $tag >.git/refs/tags/wrong &&
|
||||
test_when_finished "git update-ref -d refs/tags/wrong" &&
|
||||
@ -558,7 +558,7 @@ test_expect_success 'rev-list --verify-objects with commit graph (parent)' '
|
||||
test_expect_success 'force fsck to ignore double author' '
|
||||
git cat-file commit HEAD >basis &&
|
||||
sed "s/^author .*/&,&/" <basis | tr , \\n >multiple-authors &&
|
||||
new=$(git hash-object -t commit -w --stdin <multiple-authors) &&
|
||||
new=$(git hash-object --literally -t commit -w --stdin <multiple-authors) &&
|
||||
test_when_finished "remove_object $new" &&
|
||||
git update-ref refs/heads/bogus "$new" &&
|
||||
test_when_finished "git update-ref -d refs/heads/bogus" &&
|
||||
@ -573,7 +573,7 @@ test_expect_success 'fsck notices blob entry pointing to null sha1' '
|
||||
(git init null-blob &&
|
||||
cd null-blob &&
|
||||
sha=$(printf "100644 file$_bz$_bzoid" |
|
||||
git hash-object -w --stdin -t tree) &&
|
||||
git hash-object --literally -w --stdin -t tree) &&
|
||||
git fsck 2>out &&
|
||||
test_i18ngrep "warning.*null sha1" out
|
||||
)
|
||||
@ -583,7 +583,7 @@ test_expect_success 'fsck notices submodule entry pointing to null sha1' '
|
||||
(git init null-commit &&
|
||||
cd null-commit &&
|
||||
sha=$(printf "160000 submodule$_bz$_bzoid" |
|
||||
git hash-object -w --stdin -t tree) &&
|
||||
git hash-object --literally -w --stdin -t tree) &&
|
||||
git fsck 2>out &&
|
||||
test_i18ngrep "warning.*null sha1" out
|
||||
)
|
||||
@ -648,7 +648,7 @@ test_expect_success 'NUL in commit' '
|
||||
git commit --allow-empty -m "initial commitQNUL after message" &&
|
||||
git cat-file commit HEAD >original &&
|
||||
q_to_nul <original >munged &&
|
||||
git hash-object -w -t commit --stdin <munged >name &&
|
||||
git hash-object --literally -w -t commit --stdin <munged >name &&
|
||||
git branch bad $(cat name) &&
|
||||
|
||||
test_must_fail git -c fsck.nulInCommit=error fsck 2>warn.1 &&
|
||||
@ -794,8 +794,8 @@ test_expect_success 'fsck errors in packed objects' '
|
||||
git cat-file commit HEAD >basis &&
|
||||
sed "s/</one/" basis >one &&
|
||||
sed "s/</foo/" basis >two &&
|
||||
one=$(git hash-object -t commit -w one) &&
|
||||
two=$(git hash-object -t commit -w two) &&
|
||||
one=$(git hash-object --literally -t commit -w one) &&
|
||||
two=$(git hash-object --literally -t commit -w two) &&
|
||||
pack=$(
|
||||
{
|
||||
echo $one &&
|
||||
|
140
t/t1451-fsck-buffer.sh
Executable file
140
t/t1451-fsck-buffer.sh
Executable file
@ -0,0 +1,140 @@
|
||||
#!/bin/sh
|
||||
|
||||
test_description='fsck on buffers without NUL termination
|
||||
|
||||
The goal here is to make sure that the various fsck parsers never look
|
||||
past the end of the buffer they are given, even when encountering broken
|
||||
or truncated objects.
|
||||
|
||||
We have to use "hash-object" for this because most code paths that read objects
|
||||
append an extra NUL for safety after the buffer. But hash-object, since it is
|
||||
reading straight from a file (and possibly even mmap-ing it) cannot always do
|
||||
so.
|
||||
|
||||
These tests _might_ catch such overruns in normal use, but should be run with
|
||||
ASan or valgrind for more confidence.
|
||||
'
|
||||
. ./test-lib.sh
|
||||
|
||||
# the general idea for tags and commits is to build up the "base" file
|
||||
# progressively, and then test new truncations on top of it.
|
||||
reset () {
|
||||
test_expect_success 'reset input to empty' '
|
||||
>base
|
||||
'
|
||||
}
|
||||
|
||||
add () {
|
||||
content="$1"
|
||||
type=${content%% *}
|
||||
test_expect_success "add $type line" '
|
||||
echo "$content" >>base
|
||||
'
|
||||
}
|
||||
|
||||
check () {
|
||||
type=$1
|
||||
fsck=$2
|
||||
content=$3
|
||||
test_expect_success "truncated $type ($fsck, \"$content\")" '
|
||||
# do not pipe into hash-object here; we want to increase
|
||||
# the chance that it uses a fixed-size buffer or mmap,
|
||||
# and a pipe would be read into a strbuf.
|
||||
{
|
||||
cat base &&
|
||||
echo "$content"
|
||||
} >input &&
|
||||
test_must_fail git hash-object -t "$type" input 2>err &&
|
||||
grep "$fsck" err
|
||||
'
|
||||
}
|
||||
|
||||
test_expect_success 'create valid objects' '
|
||||
git commit --allow-empty -m foo &&
|
||||
commit=$(git rev-parse --verify HEAD) &&
|
||||
tree=$(git rev-parse --verify HEAD^{tree})
|
||||
'
|
||||
|
||||
reset
|
||||
check commit missingTree ""
|
||||
check commit missingTree "tr"
|
||||
check commit missingTree "tree"
|
||||
check commit badTreeSha1 "tree "
|
||||
check commit badTreeSha1 "tree 1234"
|
||||
add "tree $tree"
|
||||
|
||||
# these expect missingAuthor because "parent" is optional
|
||||
check commit missingAuthor ""
|
||||
check commit missingAuthor "par"
|
||||
check commit missingAuthor "parent"
|
||||
check commit badParentSha1 "parent "
|
||||
check commit badParentSha1 "parent 1234"
|
||||
add "parent $commit"
|
||||
|
||||
check commit missingAuthor ""
|
||||
check commit missingAuthor "au"
|
||||
check commit missingAuthor "author"
|
||||
ident_checks () {
|
||||
check $1 missingEmail "$2 "
|
||||
check $1 missingEmail "$2 name"
|
||||
check $1 badEmail "$2 name <"
|
||||
check $1 badEmail "$2 name <email"
|
||||
check $1 missingSpaceBeforeDate "$2 name <email>"
|
||||
check $1 badDate "$2 name <email> "
|
||||
check $1 badDate "$2 name <email> 1234"
|
||||
check $1 badTimezone "$2 name <email> 1234 "
|
||||
check $1 badTimezone "$2 name <email> 1234 +"
|
||||
}
|
||||
ident_checks commit author
|
||||
add "author name <email> 1234 +0000"
|
||||
|
||||
check commit missingCommitter ""
|
||||
check commit missingCommitter "co"
|
||||
check commit missingCommitter "committer"
|
||||
ident_checks commit committer
|
||||
add "committer name <email> 1234 +0000"
|
||||
|
||||
reset
|
||||
check tag missingObject ""
|
||||
check tag missingObject "obj"
|
||||
check tag missingObject "object"
|
||||
check tag badObjectSha1 "object "
|
||||
check tag badObjectSha1 "object 1234"
|
||||
add "object $commit"
|
||||
|
||||
check tag missingType ""
|
||||
check tag missingType "ty"
|
||||
check tag missingType "type"
|
||||
check tag badType "type "
|
||||
check tag badType "type com"
|
||||
add "type commit"
|
||||
|
||||
check tag missingTagEntry ""
|
||||
check tag missingTagEntry "ta"
|
||||
check tag missingTagEntry "tag"
|
||||
check tag badTagName "tag "
|
||||
add "tag foo"
|
||||
|
||||
check tag missingTagger ""
|
||||
check tag missingTagger "ta"
|
||||
check tag missingTagger "tagger"
|
||||
ident_checks tag tagger
|
||||
|
||||
# trees are a binary format and can't use our earlier helpers
|
||||
test_expect_success 'truncated tree (short hash)' '
|
||||
printf "100644 foo\0\1\1\1\1" >input &&
|
||||
test_must_fail git hash-object -t tree input 2>err &&
|
||||
grep badTree err
|
||||
'
|
||||
|
||||
test_expect_success 'truncated tree (missing nul)' '
|
||||
# these two things are indistinguishable to the parser. The important
|
||||
# thing about this is example is that there are enough bytes to
|
||||
# make up a hash, and that there is no NUL (and we confirm that the
|
||||
# parser does not walk past the end of the buffer).
|
||||
printf "100644 a long filename, or a hash with missing nul?" >input &&
|
||||
test_must_fail git hash-object -t tree input 2>err &&
|
||||
grep badTree err
|
||||
'
|
||||
|
||||
test_done
|
@ -10,7 +10,7 @@ test_expect_success 'create bogus tree' '
|
||||
bogus_tree=$(
|
||||
printf "100644 fooQ$name" |
|
||||
q_to_nul |
|
||||
git hash-object -w --stdin -t tree
|
||||
git hash-object --literally -w --stdin -t tree
|
||||
)
|
||||
'
|
||||
|
||||
|
@ -29,7 +29,7 @@ make_tree () {
|
||||
make_tree_entry "$1" "$2" "$3"
|
||||
shift; shift; shift
|
||||
done |
|
||||
git hash-object -w -t tree --stdin
|
||||
git hash-object --literally -w -t tree --stdin
|
||||
}
|
||||
|
||||
# this is kind of a convoluted setup, but matches
|
||||
|
@ -10,7 +10,7 @@ test_expect_success 'setup' '
|
||||
|
||||
git cat-file commit HEAD |
|
||||
sed "/^author /s/>/>-<>/" >broken_email.commit &&
|
||||
git hash-object -w -t commit broken_email.commit >broken_email.hash &&
|
||||
git hash-object --literally -w -t commit broken_email.commit >broken_email.hash &&
|
||||
git update-ref refs/heads/broken_email $(cat broken_email.hash)
|
||||
'
|
||||
|
||||
@ -46,7 +46,7 @@ test_expect_success 'git log --format with broken author email' '
|
||||
munge_author_date () {
|
||||
git cat-file commit "$1" >commit.orig &&
|
||||
sed "s/^\(author .*>\) [0-9]*/\1 $2/" <commit.orig >commit.munge &&
|
||||
git hash-object -w -t commit commit.munge
|
||||
git hash-object --literally -w -t commit commit.munge
|
||||
}
|
||||
|
||||
test_expect_success 'unparsable dates produce sentinel value' '
|
||||
|
@ -263,7 +263,7 @@ tag guten tag
|
||||
This is an invalid tag.
|
||||
EOF
|
||||
|
||||
tag=$(git hash-object -t tag -w --stdin <wrong-tag) &&
|
||||
tag=$(git hash-object -t tag -w --stdin --literally <wrong-tag) &&
|
||||
pack1=$(echo $tag $sha | git pack-objects tag-test) &&
|
||||
echo remove tag object &&
|
||||
thirtyeight=${tag#??} &&
|
||||
|
@ -138,7 +138,7 @@ This commit object intentionally broken
|
||||
EOF
|
||||
|
||||
test_expect_success 'setup bogus commit' '
|
||||
commit="$(git hash-object -t commit -w --stdin <bogus-commit)"
|
||||
commit="$(git hash-object --literally -t commit -w --stdin <bogus-commit)"
|
||||
'
|
||||
|
||||
test_expect_success 'fsck with no skipList input' '
|
||||
|
@ -1114,7 +1114,7 @@ test_expect_success 'packfile-uri with transfer.fsckobjects fails on bad object'
|
||||
|
||||
This commit object intentionally broken
|
||||
EOF
|
||||
BOGUS=$(git -C "$P" hash-object -t commit -w --stdin <bogus-commit) &&
|
||||
BOGUS=$(git -C "$P" hash-object -t commit -w --stdin --literally <bogus-commit) &&
|
||||
git -C "$P" branch bogus-branch "$BOGUS" &&
|
||||
|
||||
echo my-blob >"$P/my-blob" &&
|
||||
|
@ -606,7 +606,7 @@ test_expect_success 'create tag without tagger' '
|
||||
git tag -a -m "Broken tag" taggerless &&
|
||||
git tag -f taggerless $(git cat-file tag taggerless |
|
||||
sed -e "/^tagger /d" |
|
||||
git hash-object --stdin -w -t tag)
|
||||
git hash-object --literally --stdin -w -t tag)
|
||||
'
|
||||
|
||||
test_atom refs/tags/taggerless type 'commit'
|
||||
|
@ -115,7 +115,7 @@ test_expect_success GPGSM 'verify and show signatures x509 with high minTrustLev
|
||||
|
||||
test_expect_success GPG 'detect fudged signature' '
|
||||
git cat-file tag seventh-signed >raw &&
|
||||
sed -e "/^tag / s/seventh/7th forged/" raw >forged1 &&
|
||||
sed -e "/^tag / s/seventh/7th-forged/" raw >forged1 &&
|
||||
git hash-object -w -t tag forged1 >forged1.tag &&
|
||||
test_must_fail git verify-tag $(cat forged1.tag) 2>actual1 &&
|
||||
grep "BAD signature from" actual1 &&
|
||||
|
@ -125,7 +125,7 @@ test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'verify-tag failes with tag date ou
|
||||
test_expect_success GPGSSH 'detect fudged ssh signature' '
|
||||
test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
|
||||
git cat-file tag seventh-signed >raw &&
|
||||
sed -e "/^tag / s/seventh/7th forged/" raw >forged1 &&
|
||||
sed -e "/^tag / s/seventh/7th-forged/" raw >forged1 &&
|
||||
git hash-object -w -t tag forged1 >forged1.tag &&
|
||||
test_must_fail git verify-tag $(cat forged1.tag) 2>actual1 &&
|
||||
grep "${GPGSSH_BAD_SIGNATURE}" actual1 &&
|
||||
|
@ -105,7 +105,7 @@ test_expect_success '--amend option with empty author' '
|
||||
test_expect_success '--amend option with missing author' '
|
||||
git cat-file commit Initial >tmp &&
|
||||
sed "s/author [^<]* </author </" tmp >malformed &&
|
||||
sha=$(git hash-object -t commit -w malformed) &&
|
||||
sha=$(git hash-object --literally -t commit -w malformed) &&
|
||||
test_when_finished "remove_object $sha" &&
|
||||
git checkout $sha &&
|
||||
test_when_finished "git checkout Initial" &&
|
||||
|
@ -202,7 +202,7 @@ test_expect_success GPG 'detect fudged signature with NUL' '
|
||||
git cat-file commit seventh-signed >raw &&
|
||||
cat raw >forged2 &&
|
||||
echo Qwik | tr "Q" "\000" >>forged2 &&
|
||||
git hash-object -w -t commit forged2 >forged2.commit &&
|
||||
git hash-object --literally -w -t commit forged2 >forged2.commit &&
|
||||
test_must_fail git verify-commit $(cat forged2.commit) &&
|
||||
git show --pretty=short --show-signature $(cat forged2.commit) >actual2 &&
|
||||
grep "BAD signature from" actual2 &&
|
||||
|
@ -270,7 +270,7 @@ test_expect_success GPGSSH 'detect fudged signature with NUL' '
|
||||
git cat-file commit seventh-signed >raw &&
|
||||
cat raw >forged2 &&
|
||||
echo Qwik | tr "Q" "\000" >>forged2 &&
|
||||
git hash-object -w -t commit forged2 >forged2.commit &&
|
||||
git hash-object --literally -w -t commit forged2 >forged2.commit &&
|
||||
test_must_fail git verify-commit $(cat forged2.commit) &&
|
||||
git show --pretty=short --show-signature $(cat forged2.commit) >actual2 &&
|
||||
grep "${GPGSSH_BAD_SIGNATURE}" actual2 &&
|
||||
|
@ -201,7 +201,7 @@ committer David Reiss <dreiss@facebook.com> 1234567890 +0000
|
||||
|
||||
some message
|
||||
EOF
|
||||
COMMIT=$(git hash-object -t commit -w badcommit) &&
|
||||
COMMIT=$(git hash-object --literally -t commit -w badcommit) &&
|
||||
git --no-pager blame $COMMIT -- uno >/dev/null
|
||||
'
|
||||
|
||||
|
@ -373,7 +373,7 @@ EOF
|
||||
|
||||
test_expect_success 'cope with tagger-less tags' '
|
||||
|
||||
TAG=$(git hash-object -t tag -w tag-content) &&
|
||||
TAG=$(git hash-object --literally -t tag -w tag-content) &&
|
||||
git update-ref refs/tags/sonnenschein $TAG &&
|
||||
git fast-export -C -C --signed-tags=strip --all > output &&
|
||||
test $(grep -c "^tag " output) = 4 &&
|
||||
|
Loading…
Reference in New Issue
Block a user