Skip to content

diffviewer: stop reading past the end of a line, and free the word diff - #5153

Open
ilia-maslakov wants to merge 2 commits into
MidnightCommander:masterfrom
ilia-maslakov:fix-diffviewer-hdiff-leak-and-overrun
Open

diffviewer: stop reading past the end of a line, and free the word diff#5153
ilia-maslakov wants to merge 2 commits into
MidnightCommander:masterfrom
ilia-maslakov:fix-diffviewer-hdiff-leak-and-overrun

Conversation

@ilia-maslakov

Copy link
Copy Markdown
Contributor

Problem

Two defects in the diff viewer, both reached by simply opening a diff.

dview_str_utf8_offset_to_pos() reads past the end of the line. It is asked
for the byte offset of a given column, and the column it is asked for is the
width of the pane. A line shorter than that is walked past the end:
g_utf8_offset_to_pointer() counts out the characters it is asked for without
looking for the end of the string.

Invalid read of size 1
   at 0x4CE8C18: g_utf8_offset_to_pointer
   by 0x1A919A: dview_str_utf8_offset_to_pos (ydiff.c:623)
   by 0x1AC4DA: dview_display_file (ydiff.c:2602)
   by 0x1ACC10: dview_update (ydiff.c:2799)

hdiff_multi() drops the array of common substrings. ret is freed only
where the function found something and every recursion below it succeeded. The
ordinary case — nothing in common, which is how the recursion ends — falls
through to the bottom of the function and loses it, as do the three
return FALSE paths.

Change

Past the end of the line the rest is padding, one byte to a column, which is the
number g_utf8_offset_to_pointer() was arriving at anyway by stepping over
whatever followed the string; it is now computed instead of walked to. The
rendering is unchanged — I compared the screen dumps of a diff before and after,
byte for byte.

hdiff_multi() keeps the result in a local and frees ret on the way out of
every path.

Verified

Opening a diff, scrolling it, switching the layout and quitting, under
valgrind --leak-check=full --errors-for-leak-kinds=definite:

invalid reads leaked blocks
before 8 9
after 0 0

Ubuntu 24.04, --enable-werror: no compiler warnings, make check 69/69.

ret holds the common substrings the recursion found, and it is freed only where
there were some and every call below returned success.  The ordinary case, where
nothing is in common and the recursion ends, falls through to the bottom of the
function and loses it, as do the three failure paths.

Signed-off-by: Ilia Maslakov <il.smind@gmail.com>
dview_str_utf8_offset_to_pos() is asked for the byte at a column, and the column
it is asked for is the width of the pane; a line is usually shorter than that.
g_utf8_offset_to_pointer() counts out the characters it is asked for without
looking for the end of the string, so it walked over whatever followed the line.

Past the end the rest is padding, a byte to a column, which is the number it was
arriving at anyway; compute it instead of walking to it.  What is drawn does not
change.

Signed-off-by: Ilia Maslakov <il.smind@gmail.com>
@github-actions github-actions Bot added this to the Future Releases milestone Sep 11, 2026
@github-actions github-actions Bot added needs triage Needs triage by maintainers prio: medium Has the potential to affect progress labels Sep 11, 2026
@mc-worker

Copy link
Copy Markdown
Contributor

dview_str_utf8_offset_to_pos is similar to str_utf8_offset_to_pos. We should str_utf8_offset_to_pos in diffviewer and fix it not dview_str_utf8_offset_to_pos.
I created 5153_diffview_stop_read_after_eol branch with such modifications.

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

Labels

needs triage Needs triage by maintainers prio: medium Has the potential to affect progress

Development

Successfully merging this pull request may close these issues.

2 participants