guri: Fix buffer overrun when decoding %-encoded URI components

There is a limited (1 or 2 byte) read off the end of the buffer if its
final or penultimate byte is `%` and it’s not nul-terminated after that.
If the buffer *is* nul-terminated then the first `g_ascii_isxdigit()`
call safely returns `FALSE` and the code moves on.

Fix it by adding an additional check, and some unit tests to catch the
behaviour.

This bug is present in libsoup, which `GUri` is based on, but not
exploitable due to how the external API only exposes nul-terminated
strings. See GNOME/libsoup!126
for the fix there.

oss-fuzz#23815
oss-fuzz#23818

Signed-off-by: Philip Withnall <withnall@endlessm.com>
14 jobs for ossfuzz-23815-23818-uri-overflows in 30 minutes and 58 seconds (queued for 1 second)
Status Job ID Name Coverage
  Style Check
passed #791187
check-todos

00:00:55

failed #791186
allowed to fail
style-check-diff

00:00:52

 
  Build
passed #791192
cross-android_api21_arm64

00:01:48

passed #791193
cross-android_api28_arm64

00:01:23

passed #791194
cross-mingw64

00:07:06

passed #791189
debian-stable-x86_64

00:02:35

passed #791188
fedora-x86_64

00:09:23

passed #791191
G_DISABLE_ASSERT

00:11:08

passed #791190
installed-tests

00:14:59

passed #791195
win32-ps
msys2-mingw32

00:07:14

passed #791196
win32-ps
vs2017-x64

00:05:49

 
  Coverage
passed #791197
coverage

00:01:46

78.3%
 
  Analysis
passed #791199
scan-build

00:07:32

failed #791198
allowed to fail
valgrind

00:13:16

 
Name Stage Failure
failed
valgrind Analysis
Uploading artifacts...
_build/config.h: found 1 matching files

_build/glib/glibconfig.h: found 1 matching files

_build/meson-logs: found 7 matching files

Uploading artifacts to coordinator... ok
id=791198 responseStatus=201 Created token=LgavEx1T
Uploading artifacts...
_build/valgrind-report.xml: found 1 matching files

Uploading artifacts to coordinator... ok
id=791198 responseStatus=201 Created token=LgavEx1T
ERROR: Job failed: exit code 1
failed
style-check-diff Style Check
         {
- uri_len = strlen (tests[i].uri); /* no trailing nul */
+ uri_len = strlen (tests[i].uri); /* no trailing nul */
uri = g_memdup (tests[i].uri, uri_len);
}

Running after_script
Uploading artifacts for failed job
ERROR: Job failed: exit code 1