Skip to content

Commit

Permalink
Sync with 2.39.1
Browse files Browse the repository at this point in the history
  • Loading branch information
gitster committed Jan 16, 2023
2 parents 262c45b + 01443f0 commit 508386c
Show file tree
Hide file tree
Showing 22 changed files with 572 additions and 125 deletions.
86 changes: 86 additions & 0 deletions Documentation/RelNotes/2.30.7.txt
@@ -0,0 +1,86 @@
Git v2.30.7 Release Notes
=========================

This release addresses the security issues CVE-2022-41903 and
CVE-2022-23521.


Fixes since v2.30.6
-------------------

* CVE-2022-41903:

git log has the ability to display commits using an arbitrary
format with its --format specifiers. This functionality is also
exposed to git archive via the export-subst gitattribute.

When processing the padding operators (e.g., %<(, %<|(, %>(,
%>>(, or %><( ), an integer overflow can occur in
pretty.c::format_and_pad_commit() where a size_t is improperly
stored as an int, and then added as an offset to a subsequent
memcpy() call.

This overflow can be triggered directly by a user running a
command which invokes the commit formatting machinery (e.g., git
log --format=...). It may also be triggered indirectly through
git archive via the export-subst mechanism, which expands format
specifiers inside of files within the repository during a git
archive.

This integer overflow can result in arbitrary heap writes, which
may result in remote code execution.

* CVE-2022-23521:

gitattributes are a mechanism to allow defining attributes for
paths. These attributes can be defined by adding a `.gitattributes`
file to the repository, which contains a set of file patterns and
the attributes that should be set for paths matching this pattern.

When parsing gitattributes, multiple integer overflows can occur
when there is a huge number of path patterns, a huge number of
attributes for a single pattern, or when the declared attribute
names are huge.

These overflows can be triggered via a crafted `.gitattributes` file
that may be part of the commit history. Git silently splits lines
longer than 2KB when parsing gitattributes from a file, but not when
parsing them from the index. Consequentially, the failure mode
depends on whether the file exists in the working tree, the index or
both.

This integer overflow can result in arbitrary heap reads and writes,
which may result in remote code execution.

Credit for finding CVE-2022-41903 goes to Joern Schneeweisz of GitLab.
An initial fix was authored by Markus Vervier of X41 D-Sec. Credit for
finding CVE-2022-23521 goes to Markus Vervier and Eric Sesterhenn of X41
D-Sec. This work was sponsored by OSTIF.

The proposed fixes have been polished and extended to cover additional
findings by Patrick Steinhardt of GitLab, with help from others on the
Git security mailing list.

Patrick Steinhardt (21):
attr: fix overflow when upserting attribute with overly long name
attr: fix out-of-bounds read with huge attribute names
attr: fix integer overflow when parsing huge attribute names
attr: fix out-of-bounds write when parsing huge number of attributes
attr: fix out-of-bounds read with unreasonable amount of patterns
attr: fix integer overflow with more than INT_MAX macros
attr: harden allocation against integer overflows
attr: fix silently splitting up lines longer than 2048 bytes
attr: ignore attribute lines exceeding 2048 bytes
attr: ignore overly large gitattributes files
pretty: fix out-of-bounds write caused by integer overflow
pretty: fix out-of-bounds read when left-flushing with stealing
pretty: fix out-of-bounds read when parsing invalid padding format
pretty: fix adding linefeed when placeholder is not expanded
pretty: fix integer overflow in wrapping format
utf8: fix truncated string lengths in `utf8_strnwidth()`
utf8: fix returning negative string width
utf8: fix overflow when returning string width
utf8: fix checking for glyph width in `strbuf_utf8_replace()`
utf8: refactor `strbuf_utf8_replace` to not rely on preallocated buffer
pretty: restrict input lengths for padding and wrapping formats

5 changes: 5 additions & 0 deletions Documentation/RelNotes/2.31.6.txt
@@ -0,0 +1,5 @@
Git v2.31.6 Release Notes
=========================

This release merges the security fix that appears in v2.30.7; see
the release notes for that version for details.
8 changes: 8 additions & 0 deletions Documentation/RelNotes/2.32.5.txt
@@ -0,0 +1,8 @@
Git v2.32.5 Release Notes
=========================

This release merges the security fix that appears in v2.30.7; see
the release notes for that version for details.

In addition, included are additional code for "git fsck" to check
for questionable .gitattributes files.
5 changes: 5 additions & 0 deletions Documentation/RelNotes/2.33.6.txt
@@ -0,0 +1,5 @@
Git v2.33.6 Release Notes
=========================

This release merges the security fix that appears in v2.30.7; see
the release notes for that version for details.
5 changes: 5 additions & 0 deletions Documentation/RelNotes/2.34.6.txt
@@ -0,0 +1,5 @@
Git v2.34.6 Release Notes
=========================

This release merges the security fix that appears in v2.30.7; see
the release notes for that version for details.
5 changes: 5 additions & 0 deletions Documentation/RelNotes/2.35.6.txt
@@ -0,0 +1,5 @@
Git v2.35.6 Release Notes
=========================

This release merges the security fix that appears in v2.30.7; see
the release notes for that version for details.
5 changes: 5 additions & 0 deletions Documentation/RelNotes/2.36.4.txt
@@ -0,0 +1,5 @@
Git v2.36.4 Release Notes
=========================

This release merges the security fix that appears in v2.30.7; see
the release notes for that version for details.
5 changes: 5 additions & 0 deletions Documentation/RelNotes/2.37.5.txt
@@ -0,0 +1,5 @@
Git v2.37.5 Release Notes
=========================

This release merges the security fix that appears in v2.30.7; see
the release notes for that version for details.
5 changes: 5 additions & 0 deletions Documentation/RelNotes/2.38.3.txt
@@ -0,0 +1,5 @@
Git v2.38.3 Release Notes
=========================

This release merges the security fix that appears in v2.30.7; see
the release notes for that version for details.
5 changes: 5 additions & 0 deletions Documentation/RelNotes/2.39.1.txt
@@ -0,0 +1,5 @@
Git v2.39.1 Release Notes
=========================

This release merges the security fix that appears in v2.30.7; see
the release notes for that version for details.
91 changes: 56 additions & 35 deletions attr.c
Expand Up @@ -24,7 +24,7 @@ static const char git_attr__unknown[] = "(builtin)unknown";
#define ATTR__UNKNOWN git_attr__unknown

struct git_attr {
int attr_nr; /* unique attribute number */
unsigned int attr_nr; /* unique attribute number */
char name[FLEX_ARRAY]; /* attribute name */
};

Expand Down Expand Up @@ -206,7 +206,7 @@ static void report_invalid_attr(const char *name, size_t len,
* dictionary. If no entry is found, create a new attribute and store it in
* the dictionary.
*/
static const struct git_attr *git_attr_internal(const char *name, int namelen)
static const struct git_attr *git_attr_internal(const char *name, size_t namelen)
{
struct git_attr *a;

Expand All @@ -222,8 +222,8 @@ static const struct git_attr *git_attr_internal(const char *name, int namelen)
a->attr_nr = hashmap_get_size(&g_attr_hashmap.map);

attr_hashmap_add(&g_attr_hashmap, a->name, namelen, a);
assert(a->attr_nr ==
(hashmap_get_size(&g_attr_hashmap.map) - 1));
if (a->attr_nr != hashmap_get_size(&g_attr_hashmap.map) - 1)
die(_("unable to add additional attribute"));
}

hashmap_unlock(&g_attr_hashmap);
Expand Down Expand Up @@ -268,7 +268,7 @@ struct match_attr {
const struct git_attr *attr;
} u;
char is_macro;
unsigned num_attr;
size_t num_attr;
struct attr_state state[FLEX_ARRAY];
};

Expand All @@ -289,7 +289,7 @@ static const char *parse_attr(const char *src, int lineno, const char *cp,
struct attr_state *e)
{
const char *ep, *equals;
int len;
size_t len;

ep = cp + strcspn(cp, blank);
equals = strchr(cp, '=');
Expand Down Expand Up @@ -333,8 +333,7 @@ static const char *parse_attr(const char *src, int lineno, const char *cp,
static struct match_attr *parse_attr_line(const char *line, const char *src,
int lineno, unsigned flags)
{
int namelen;
int num_attr, i;
size_t namelen, num_attr, i;
const char *cp, *name, *states;
struct match_attr *res = NULL;
int is_macro;
Expand All @@ -345,6 +344,11 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,
return NULL;
name = cp;

if (strlen(line) >= ATTR_MAX_LINE_LENGTH) {
warning(_("ignoring overly long attributes line %d"), lineno);
return NULL;
}

if (*cp == '"' && !unquote_c_style(&pattern, name, &states)) {
name = pattern.buf;
namelen = pattern.len;
Expand Down Expand Up @@ -381,10 +385,9 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,
goto fail_return;
}

res = xcalloc(1,
sizeof(*res) +
sizeof(struct attr_state) * num_attr +
(is_macro ? 0 : namelen + 1));
res = xcalloc(1, st_add3(sizeof(*res),
st_mult(sizeof(struct attr_state), num_attr),
is_macro ? 0 : namelen + 1));
if (is_macro) {
res->u.attr = git_attr_internal(name, namelen);
} else {
Expand Down Expand Up @@ -447,11 +450,12 @@ struct attr_stack {

static void attr_stack_free(struct attr_stack *e)
{
int i;
unsigned i;
free(e->origin);
for (i = 0; i < e->num_matches; i++) {
struct match_attr *a = e->attrs[i];
int j;
size_t j;

for (j = 0; j < a->num_attr; j++) {
const char *setto = a->state[j].setto;
if (setto == ATTR__TRUE ||
Expand Down Expand Up @@ -660,8 +664,8 @@ static void handle_attr_line(struct attr_stack *res,
a = parse_attr_line(line, src, lineno, flags);
if (!a)
return;
ALLOC_GROW(res->attrs, res->num_matches + 1, res->alloc);
res->attrs[res->num_matches++] = a;
ALLOC_GROW_BY(res->attrs, res->num_matches, 1, res->alloc);
res->attrs[res->num_matches - 1] = a;
}

static struct attr_stack *read_attr_from_array(const char **list)
Expand Down Expand Up @@ -701,11 +705,12 @@ void git_attr_set_direction(enum git_attr_direction new_direction)

static struct attr_stack *read_attr_from_file(const char *path, unsigned flags)
{
struct strbuf buf = STRBUF_INIT;
int fd;
FILE *fp;
struct attr_stack *res;
char buf[2048];
int lineno = 0;
struct stat st;

if (flags & READ_ATTR_NOFOLLOW)
fd = open_nofollow(path, O_RDONLY);
Expand All @@ -717,15 +722,26 @@ static struct attr_stack *read_attr_from_file(const char *path, unsigned flags)
return NULL;
}
fp = xfdopen(fd, "r");
if (fstat(fd, &st)) {
warning_errno(_("cannot fstat gitattributes file '%s'"), path);
fclose(fp);
return NULL;
}
if (st.st_size >= ATTR_MAX_FILE_SIZE) {
warning(_("ignoring overly large gitattributes file '%s'"), path);
fclose(fp);
return NULL;
}

CALLOC_ARRAY(res, 1);
while (fgets(buf, sizeof(buf), fp)) {
char *bufp = buf;
if (!lineno)
skip_utf8_bom(&bufp, strlen(bufp));
handle_attr_line(res, bufp, path, ++lineno, flags);
while (strbuf_getline(&buf, fp) != EOF) {
if (!lineno && starts_with(buf.buf, utf8_bom))
strbuf_remove(&buf, 0, strlen(utf8_bom));
handle_attr_line(res, buf.buf, path, ++lineno, flags);
}

fclose(fp);
strbuf_release(&buf);
return res;
}

Expand All @@ -736,6 +752,7 @@ static struct attr_stack *read_attr_from_index(struct index_state *istate,
struct attr_stack *res;
char *buf, *sp;
int lineno = 0;
size_t size;

if (!istate)
return NULL;
Expand All @@ -754,9 +771,13 @@ static struct attr_stack *read_attr_from_index(struct index_state *istate,
if (!path_in_cone_mode_sparse_checkout(path, istate))
return NULL;

buf = read_blob_data_from_index(istate, path, NULL);
buf = read_blob_data_from_index(istate, path, &size);
if (!buf)
return NULL;
if (size >= ATTR_MAX_FILE_SIZE) {
warning(_("ignoring overly large gitattributes blob '%s'"), path);
return NULL;
}

CALLOC_ARRAY(res, 1);
for (sp = buf; *sp; ) {
Expand Down Expand Up @@ -999,12 +1020,12 @@ static int macroexpand_one(struct all_attrs_item *all_attrs, int nr, int rem);
static int fill_one(struct all_attrs_item *all_attrs,
const struct match_attr *a, int rem)
{
int i;
size_t i;

for (i = a->num_attr - 1; rem > 0 && i >= 0; i--) {
const struct git_attr *attr = a->state[i].attr;
for (i = a->num_attr; rem > 0 && i > 0; i--) {
const struct git_attr *attr = a->state[i - 1].attr;
const char **n = &(all_attrs[attr->attr_nr].value);
const char *v = a->state[i].setto;
const char *v = a->state[i - 1].setto;

if (*n == ATTR__UNKNOWN) {
*n = v;
Expand All @@ -1020,11 +1041,11 @@ static int fill(const char *path, int pathlen, int basename_offset,
struct all_attrs_item *all_attrs, int rem)
{
for (; rem > 0 && stack; stack = stack->prev) {
int i;
unsigned i;
const char *base = stack->origin ? stack->origin : "";

for (i = stack->num_matches - 1; 0 < rem && 0 <= i; i--) {
const struct match_attr *a = stack->attrs[i];
for (i = stack->num_matches; 0 < rem && 0 < i; i--) {
const struct match_attr *a = stack->attrs[i - 1];
if (a->is_macro)
continue;
if (path_matches(path, pathlen, basename_offset,
Expand Down Expand Up @@ -1055,11 +1076,11 @@ static void determine_macros(struct all_attrs_item *all_attrs,
const struct attr_stack *stack)
{
for (; stack; stack = stack->prev) {
int i;
for (i = stack->num_matches - 1; i >= 0; i--) {
const struct match_attr *ma = stack->attrs[i];
unsigned i;
for (i = stack->num_matches; i > 0; i--) {
const struct match_attr *ma = stack->attrs[i - 1];
if (ma->is_macro) {
int n = ma->u.attr->attr_nr;
unsigned int n = ma->u.attr->attr_nr;
if (!all_attrs[n].macro) {
all_attrs[n].macro = ma;
}
Expand Down Expand Up @@ -1111,7 +1132,7 @@ void git_check_attr(struct index_state *istate,
collect_some_attrs(istate, path, check);

for (i = 0; i < check->nr; i++) {
size_t n = check->items[i].attr->attr_nr;
unsigned int n = check->items[i].attr->attr_nr;
const char *value = check->all_attrs[n].value;
if (value == ATTR__UNKNOWN)
value = ATTR__UNSET;
Expand Down
12 changes: 12 additions & 0 deletions attr.h
Expand Up @@ -107,6 +107,18 @@
* - Free the `attr_check` struct by calling `attr_check_free()`.
*/

/**
* The maximum line length for a gitattributes file. If the line exceeds this
* length we will ignore it.
*/
#define ATTR_MAX_LINE_LENGTH 2048

/**
* The maximum size of the giattributes file. If the file exceeds this size we
* will ignore it.
*/
#define ATTR_MAX_FILE_SIZE (100 * 1024 * 1024)

struct index_state;

/**
Expand Down

0 comments on commit 508386c

Please sign in to comment.