Skip to content

Sanitize the hunk header to ensure it contains UTF-8 valid data#4542

Merged
pks-t merged 1 commit into
libgit2:masterfrom
stanhu:sh-sanitize-utf8-hunk-header
May 7, 2018
Merged

Sanitize the hunk header to ensure it contains UTF-8 valid data#4542
pks-t merged 1 commit into
libgit2:masterfrom
stanhu:sh-sanitize-utf8-hunk-header

Conversation

@stanhu

@stanhu stanhu commented Feb 23, 2018

Copy link
Copy Markdown
Contributor

The diff driver truncates the hunk header text to 80 bytes, which
can cut off 4-byte Unicode characters and introduce garbage characters in the
diff output. This change sanitizes the hunk header before it is displayed.

This is consistent with what git does: https://github.com/git/git/blob/e3a80781f5932f5fea12a49eb06f3ade4ed8945c/diff.c#L1927-L1941

Closes libgit2/rugged#716

@stanhu

stanhu commented Feb 23, 2018

Copy link
Copy Markdown
Contributor Author

Does it make sense to pull in https://github.com/stanhu/rugged-encoding-test into resources and write a test against it?

@stanhu stanhu force-pushed the sh-sanitize-utf8-hunk-header branch from f67e79c to 5da325a Compare February 23, 2018 08:02

@pks-t pks-t left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your PR. I've added a few remarks on your code

Comment thread src/util.h Outdated
*/
extern int git__utf8_iterate(const uint8_t *str, int str_len, int32_t *dst);

/**

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As this is an internal function only, we do not want its documentation to end up public. Thus, we only "/*"-style comment headers for internal functions.

Comment thread src/util.c Outdated
return git__timer();
}

extern int git__utf8_valid_buf_length(const uint8_t *str, int str_len)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function shouldn't be declared "extern" in the implementation

Comment thread src/util.c
offset += length;
}

return offset;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this function should return an ssize_t instead of an int

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually think that this function should both take and return size_t values. At the moment, it's taking an int... which should be a small value, due to the truncation, but the caller isn't actually doing the truncation (which highlights the problem). It should ideally take a size_t which is what we use to measure the size of strings.

At that point, we should probably also return a size_t. Neither the inputs nor the outputs need to support negative values.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, my mistake. I was just using the prototypes from the existing UTF-8 functions.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries. Thanks for tackling this problem, @stanhu - I've got a busy day, but I'll try to take another pass at reviewing.

Comment thread src/diff_xdiff.c Outdated
info->hunk.header_len = bufs[0].size;
/* Sanitize the hunk header in case there is invalid Unicode */
buffer_len = git__utf8_valid_buf_length((const uint8_t *) bufs[0].ptr, bufs[0].size);
info->hunk.header_len = buffer_len;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So what you're doing here is cutting off at the point where there is the first invalid UTF8 codepoint. I think this solution here is actually not sufficient yet. In case info->hunk.header_len >= sizeof(info->hunk.header), you're actually stripping the last byte again. In case the last byte belongs to a multi-byte codepoint again, it will become invalid. So you actually have to sanitize after checking the header length.

But stepping back a bit: is it actually guaranteed that the header is in UTF8? Git is agnostic to the encoding in which files are being stored, so I could envision the diff machinery processing, say, UTF16-encoded data.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Git doesn't do well with UTF16. UTF16 files will be treated as binary (since there's a NUL) and will probably not have a header at all. I'm curious what would happen with non-UTF8 multibyte charsets, but I think it's good enough to emulate Git's behavior, which also seems to punt on those.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I know about them being treated as binary by default. It's possible to override that via gitattributes, though. But as you say, it's probably okay to just punt on that, then. It's only non-critical metainformation, after all.

@pks-t

pks-t commented Feb 23, 2018

Copy link
Copy Markdown
Member

Oh, and one more thing: I'd definitly welcome to have a test here, as you propose. Especially as the issue at hand is quite easy to reproduce

@stanhu stanhu force-pushed the sh-sanitize-utf8-hunk-header branch from b15e3a6 to c17e760 Compare February 24, 2018 18:37
@stanhu

stanhu commented Feb 24, 2018

Copy link
Copy Markdown
Contributor Author

Thanks. I addressed the review comments and added a test that passes locally in my environment, but for some reason the CI environment can't find the repository directory that I added.

UPDATE: Ok, figured it out. Missing a .gitkeep in the refs/ path.

@stanhu stanhu force-pushed the sh-sanitize-utf8-hunk-header branch from 8535013 to a2dff52 Compare February 25, 2018 06:06
@stanhu

stanhu commented Feb 28, 2018

Copy link
Copy Markdown
Contributor Author

@pks-t Would you mind doing another round of review? Thanks!

Comment thread src/diff_xdiff.c Outdated

/* Sanitize the hunk header in case there is invalid Unicode */
buffer_len = git__utf8_valid_buf_length((const uint8_t *) bufs[0].ptr, bufs[0].size);
info->hunk.header_len = buffer_len;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fear that this is not quite correct - previously we set info->hunk.header_len based on bufs[0].size, then truncated it if was too large to fit in info->hunk.header.

Now we're no longer doing that truncation, we're instead just setting it to the return value of git__utf8_valid_buf_length, which is looking at the valid UTF-8 character size in bufs[0].size.

The size of valid utf-8 characters in the can be larger than sizeof(info->hunk.header) (aka GIT_DIFF_HUNK_HEADER_SIZE). You can see that this change will overrun this buffer by changing GIT_DIFF_HUNK_HEADER_SIZE to a smaller value.

I thought the goal here was to cope with the truncation in the middle of a utf-8 character. In that case, should we not be giving the truncated length to the git__utf8_valid_buf_length function?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On that note, I don't understand the test then. Did you produce a file that has invalid utf-8 characters on a line, and are ensuring that they're truncated? I ask because it appears that the test is doing something, since it appears that your git__utf8_valid_buf_length function has determined that the last two bytes do not include valid bytes to make up a utf-8 character.

I expected that the test would have done the truncation at a point that produced an some bytes that were invalid UTF-8 and then truncated them. I think that test would have illustrated this problem, since there would have been no truncation at all.

But I may be misunderstanding the test...?

Comment thread src/util.c
offset += length;
}

return offset;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually think that this function should both take and return size_t values. At the moment, it's taking an int... which should be a small value, due to the truncation, but the caller isn't actually doing the truncation (which highlights the problem). It should ideally take a size_t which is what we use to measure the size of strings.

At that point, we should probably also return a size_t. Neither the inputs nor the outputs need to support negative values.

Comment thread tests/diff/patch.c Outdated
"+CSSとは異なり、zIndexはすべてのオブジェクトに対してグローバルではありません。zIndexプロパティは親レイヤに対してローカルです。詳細につきましては、コンテナチュートリアルで説明しています。 [現在作業中です: Link]。\n"
" \n"
" ### スクリーン上のスプライトの順序付け方法\n"
" これまで学んだことを生かして、画面にスプライトを表示して、zIndexの設定をしてみましょう!\n";

@ethomson ethomson Mar 1, 2018

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, we've generally tried to escape these... the source character set in some VS compilers is the currently running codepage. I'm not up to speed on the ability to use utf-8 as the source character set in C proper... I actually expect that it would work but we've tried to avoid this in the past.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, if you insist. I have to say 56cccc5 looks incredibly ugly. :)

@stanhu stanhu force-pushed the sh-sanitize-utf8-hunk-header branch 2 times, most recently from 815be7c to b0e39cb Compare March 2, 2018 04:48

@pks-t pks-t left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your fixups! Here's another round of reviews

Comment thread src/diff_xdiff.c Outdated

/* Sanitize the hunk header in case there is invalid Unicode */
buffer_len = git__utf8_valid_buf_length((const uint8_t *) bufs[0].ptr, info->hunk.header_len);
info->hunk.header_len = buffer_len;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small nit only worthwhile to fix up in case you do another round of fixes: you can avoid buffer_len by simply directly assigning to info->hunk.header_len.

Comment thread src/util.c Outdated
}

/**
* Iterate through an UTF-8 string and stops after finding any invalid UTF-8

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I think you misunderstood my initial comment on this documentation. It should definitely be part of the header, but instead of having a /** */ header, we want it to be a /* */ header (note the difference of one * vs two ** characters)

Comment thread src/util.c Outdated
size_t git__utf8_valid_buf_length(const uint8_t *str, size_t str_len)
{
size_t offset = 0;
int length = 0;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This initialization is unnecessary. You could also just directly declare the variable in the loop. It's only a minor nit, though, feel free to ignore this comment

Comment thread tests/diff/patch.c Outdated
"index dd469c9..caf022d 100644\n"
"--- a/Japanese.md\n"
"+++ b/Japanese.md\n"
"@@ -24,7 +24,7 @@ Redactedでは**レイヤ**における**zIndex**属性を制御可能にする \n"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit surprised there's a space right in front of the newline. As the test should actually show that we're correctly truncating UTF8 at the context line's cutoff point, I'd have expected the last character before the NL to be a UTF8 code point.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am confused too. I verified that the buffer truncates the length properly; not sure where this extra space comes from yet. I'll have to dig into it later.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I figured it out. Sanitizing the buffer for valid UTF-8 characters will likely remove the newline from the hunk header, so we need to add it back if it was stripped.

@stanhu

stanhu commented Apr 23, 2018

Copy link
Copy Markdown
Contributor Author

@pks-t Ok, finally found some time to figure out what was going on here and respond to your feedback. Please take a look!

@pks-t pks-t left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your changes! Code looks good, and the test results look good, too. I'd love to avoid adding yet another repository for this test, though, as the diff can simply be done between two buffers instead of using two revisions. If you could change that'd be great

Comment thread tests/diff/patch.c
git_buf_free(&content);
}

void test_diff_patch__can_strip_bad_utf8(void)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All that we want to do in this test is to just make sure that the diff of two files produces the correct result. So in fact we don't really need to add the complete test repository here, which would be a bit wasteful. Instead, you could just use git_diff_buffers.

@stanhu stanhu Apr 26, 2018

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could add the two files (https://raw.githubusercontent.com/stanhu/rugged-encoding-test/3c4f7762042ad735d97d4781c4c7ee7d4486646c/Japanese.md) and (https://raw.githubusercontent.com/stanhu/rugged-encoding-test/387914a182ae0868364ec09fadb1beb47690fd4a/Japanese.md) in as character buffers, but wouldn't this pollute the test even more since we have to escape UTF-8 characters?

Ideally I'd be able to simplify the test case so that it boils down to a few lines, but when I tried earlier it was challenging to reproduce. I can try again.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pks-t Ok, I think I figured out a better test case. I just mirrored this one: https://github.com/git/git/blob/master/t/t4025-hunk-header.sh

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, these tests are great! Thanks a lot!

@stanhu stanhu force-pushed the sh-sanitize-utf8-hunk-header branch from f5774ed to 26b37e5 Compare May 5, 2018 21:52
The diff driver truncates the hunk header text to 80 bytes, which can truncate
4-byte Unicode characters and introduce garbage characters in the diff
output. This change sanitizes the hunk header before it is displayed.

This mirrors the test in git: https://github.com/git/git/blob/master/t/t4025-hunk-header.sh

Closes libgit2/rugged#716
@stanhu stanhu force-pushed the sh-sanitize-utf8-hunk-header branch from 26b37e5 to 9d83a2b Compare May 5, 2018 21:54
@pks-t pks-t merged commit bb468ad into libgit2:master May 7, 2018
@pks-t

pks-t commented May 7, 2018

Copy link
Copy Markdown
Member

Thanks a lot for this PR!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants