Re: CVE-2016-9179
Back to Debian mailing lists.
Here is a patch to the wheezy version that appears to work for me. Based
on the upstream patches - the relevant parts seem to apply without any
major dramas - although some bits required manual processing.
This is available for testing. At:
https://people.debian.org/~bam/debian/pool/main/l/lynx-cur/
diff -Nru lynx-cur-2.8.8dev.12/debian/changelog lynx-cur-2.8.8dev.12/debian/changelog
--- lynx-cur-2.8.8dev.12/debian/changelog 2016-11-16 18:14:13.000000000 +1100
+++ lynx-cur-2.8.8dev.12/debian/changelog 2016-11-16 17:47:21.000000000 +1100
@@ -1,3 +1,10 @@
+lynx-cur (2.8.8dev.12-2+deb7u1) wheezy-security; urgency=high
+
+ * Non-maintainer upload by the LTS Team.
+ * Fix CVE-2016-9179: invalid URL parsing with '?'.
+
+ -- Brian May <bam@debian.org> Wed, 16 Nov 2016 17:45:35 +1100
+
lynx-cur (2.8.8dev.12-2) unstable; urgency=low
* Applied use-dpkg-buildflags.patch which added hardening flags for
diff -Nru lynx-cur-2.8.8dev.12/debian/patches/CVE-2016-9179-1.patch lynx-cur-2.8.8dev.12/debian/patches/CVE-2016-9179-1.patch
--- lynx-cur-2.8.8dev.12/debian/patches/CVE-2016-9179-1.patch 1970-01-01 10:00:00.000000000 +1000
+++ lynx-cur-2.8.8dev.12/debian/patches/CVE-2016-9179-1.patch 2016-11-15 18:03:57.000000000 +1100
@@ -0,0 +1,163 @@
+--- a/WWW/Library/Implementation/HTTP.c
++++ b/WWW/Library/Implementation/HTTP.c
+@@ -415,27 +415,150 @@
+ #endif /* _WINDOWS */
+
+ /*
++ * RFC-1738 says we can have user/password using these ASCII characters
++ * safe = "$" | "-" | "_" | "." | "+"
++ * extra = "!" | "*" | "'" | "(" | ")" | ","
++ * hex = digit | "A" | "B" | "C" | "D" | "E" | "F" |
++ * "a" | "b" | "c" | "d" | "e" | "f"
++ * escape = "%" hex hex
++ * unreserved = alpha | digit | safe | extra
++ * uchar = unreserved | escape
++ * user = *[ uchar | ";" | "?" | "&" | "=" ]
++ * password = *[ uchar | ";" | "?" | "&" | "=" ]
++ * and we cannot have a password without user, i.e., no leading ":"
++ * and ":", "@", "/" must be encoded, i.e., will not appear as such.
++ *
++ * However, in a URL
++ * //<user>:<password>@<host>:<port>/<url-path>
++ * valid characters in the host are different, not allowing most of those
++ * punctuation characters.
++ *
++ * RFC-3986 amends this, using
++ * userinfo = *( unreserved / pct-encoded / sub-delims / ":" )
++ * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
++ * reserved = gen-delims / sub-delims
++ * gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
++ * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
++ * / "*" / "+" / "," / ";" / "="
++ * and
++ * host = IP-literal / IPv4address / reg-name
++ * reg-name = *( unreserved / pct-encoded / sub-delims )
++ */
++#define RFC_3986_UNRESERVED(c) (isalnum(UCH(c)) || strchr("-._~", UCH(c)) != 0)
++#define RFC_3986_GEN_DELIMS(c) ((c) != 0 && strchr(":/?#[]@", UCH(c)) != 0)
++#define RFC_3986_SUB_DELIMS(c) ((c) != 0 && strchr("!$&'()*+,;=", UCH(c)) != 0)
++
++static char *skip_user_passwd(char *host)
++{
++ char *result = 0;
++ char *s = host;
++ int pass = 0;
++ int ch;
++ int last = -1;
++
++ while ((ch = UCH(*s)) != '\0') {
++ if (ch == '\0') {
++ break;
++ } else if (ch == ':') {
++ if (pass++)
++ break;
++ } else if (ch == '@') {
++ if (s != host && last != ':')
++ result = s;
++ break;
++ } else if (RFC_3986_GEN_DELIMS(ch)) {
++ if (!RFC_3986_GEN_DELIMS(s[1]))
++ break;
++ } else if (ch == '%') {
++ if (!(isxdigit(UCH(s[1])) && isxdigit(UCH(s[2]))))
++ break;
++ } else if (!(RFC_3986_UNRESERVED(ch) ||
++ RFC_3986_SUB_DELIMS(ch))) {
++ break;
++ }
++ ++s;
++ last = ch;
++ }
++ return result;
++}
++
++static char *fake_hostname(char *auth)
++{
++ char *result = NULL;
++ char *colon = NULL;
++
++ StrAllocCopy(result, auth);
++ if ((colon = strchr(result, ':')) != 0)
++ *colon = '\0';
++ if (strchr(result, '.') == 0)
++ FREE(result);
++ return result;
++}
++
++/*
+ * Strip any username from the given string so we retain only the host.
+ */
+ static void strip_userid(char *host)
+ {
+ char *p1 = host;
+- char *p2 = strchr(host, '@');
+- char *fake;
++ char *p2 = skip_user_passwd(host);
+
+ if (p2 != 0) {
++ char *msg = NULL;
++ char *auth = NULL;
++ char *save = NULL;
++ char *fake = NULL;
++ char *p3 = p2;
++ int gen_delims = 0;
++ int sub_delims = 0;
++ int my_delimit = UCH(*p2);
++ int do_trimming = (my_delimit == '@');
++
+ *p2++ = '\0';
+- if ((fake = HTParse(host, "", PARSE_HOST)) != NULL) {
+- char *msg = NULL;
++ StrAllocCopy(auth, host);
+
+- CTRACE((tfp, "parsed:%s\n", fake));
+- HTSprintf0(&msg, gettext("Address contains a username: %s"), host);
+- HTAlert(msg);
+- FREE(msg);
++ /*
++ * Trailing "gen-delims" demonstrates that there is no user/password.
++ */
++ while ((p3 != host) && RFC_3986_GEN_DELIMS(p3[-1])) {
++ ++gen_delims;
++ *(--p3) = '\0';
+ }
+- while ((*p1++ = *p2++) != '\0') {
+- ;
++ /*
++ * While legal, punctuation-only user/password is questionable.
++ */
++ while ((p3 != host) && RFC_3986_SUB_DELIMS(p3[-1])) {
++ ++sub_delims;
++ *(--p3) = '\0';
++ }
++ CTRACE((tfp, "trimmed:%s\n", host));
++ StrAllocCopy(save, host);
++
++ if (gen_delims || strcmp(save, auth)) {
++ HTSprintf0(&msg,
++ gettext("User/password may appear to be a hostname: '%s' (e.g, '%s')"),
++ auth, save);
++ do_trimming = !gen_delims;
++ } else if (*host == '\0' && sub_delims) {
++ HTSprintf0(&msg,
++ gettext("User/password contains only punctuation: %s"),
++ auth);
++ } else if ((fake = fake_hostname(host)) != NULL) {
++ HTSprintf0(&msg,
++ gettext("User/password may be confused with hostname: '%s' (e.g, '%s')"),
++ auth, fake);
++ }
++ if (msg != 0)
++ HTAlert(msg);
++ if (do_trimming) {
++ while ((*p1++ = *p2++) != '\0') {
++ ;
++ }
+ }
++ FREE(fake);
++ FREE(save);
++ FREE(auth);
++ FREE(msg);
+ }
+ }
+
diff -Nru lynx-cur-2.8.8dev.12/debian/patches/CVE-2016-9179-2.patch lynx-cur-2.8.8dev.12/debian/patches/CVE-2016-9179-2.patch
--- lynx-cur-2.8.8dev.12/debian/patches/CVE-2016-9179-2.patch 1970-01-01 10:00:00.000000000 +1000
+++ lynx-cur-2.8.8dev.12/debian/patches/CVE-2016-9179-2.patch 2016-11-16 17:40:59.000000000 +1100
@@ -0,0 +1,77 @@
+--- a/WWW/Library/Implementation/HTTCP.c
++++ b/WWW/Library/Implementation/HTTCP.c
+@@ -1581,7 +1581,6 @@
+ int status = 0;
+ char *line = NULL;
+ char *p1 = NULL;
+- char *at_sign = NULL;
+ char *host = NULL;
+
+ #ifdef INET6
+@@ -1603,14 +1602,8 @@
+ * Get node name and optional port number.
+ */
+ p1 = HTParse(url, "", PARSE_HOST);
+- if ((at_sign = strchr(p1, '@')) != NULL) {
+- /*
+- * If there's an @ then use the stuff after it as a hostname.
+- */
+- StrAllocCopy(host, (at_sign + 1));
+- } else {
+- StrAllocCopy(host, p1);
+- }
++ StrAllocCopy(host, p1);
++ strip_userid(host, FALSE);
+ FREE(p1);
+
+ HTSprintf0(&line, "%s%s", WWW_FIND_MESSAGE, host);
+--- a/WWW/Library/Implementation/HTTP.c
++++ b/WWW/Library/Implementation/HTTP.c
+@@ -498,7 +498,7 @@
+ /*
+ * Strip any username from the given string so we retain only the host.
+ */
+-static void strip_userid(char *host)
++void strip_userid(char *host, int parse_only)
+ {
+ char *p1 = host;
+ char *p2 = skip_user_passwd(host);
+@@ -548,7 +548,7 @@
+ gettext("User/password may be confused with hostname: '%s' (e.g, '%s')"),
+ auth, fake);
+ }
+- if (msg != 0)
++ if (msg != 0 && !parse_only)
+ HTAlert(msg);
+ if (do_trimming) {
+ while ((*p1++ = *p2++) != '\0') {
+@@ -1183,7 +1183,7 @@
+ char *host = NULL;
+
+ if ((host = HTParse(anAnchor->address, "", PARSE_HOST)) != NULL) {
+- strip_userid(host);
++ strip_userid(host, TRUE);
+ HTBprintf(&command, "Host: %s%c%c", host, CR, LF);
+ FREE(host);
+ }
+--- a/WWW/Library/Implementation/HTUtils.h
++++ b/WWW/Library/Implementation/HTUtils.h
+@@ -778,6 +778,8 @@
+
+ extern FILE *TraceFP(void);
+
++ extern void strip_userid(char *host, int warn);
++
+ #ifdef USE_SSL
+ extern SSL *HTGetSSLHandle(void);
+ extern void HTSSLInitPRNG(void);
+--- a/src/LYUtils.c
++++ b/src/LYUtils.c
+@@ -4616,6 +4616,7 @@
+ * Do a DNS test on the potential host field as presently trimmed. - FM
+ */
+ StrAllocCopy(host, Str);
++ strip_userid(host, FALSE);
+ HTUnEscape(host);
+ if (LYCursesON) {
+ StrAllocCopy(MsgStr, WWW_FIND_MESSAGE);
diff -Nru lynx-cur-2.8.8dev.12/debian/patches/series lynx-cur-2.8.8dev.12/debian/patches/series
--- lynx-cur-2.8.8dev.12/debian/patches/series 2016-11-16 18:14:13.000000000 +1100
+++ lynx-cur-2.8.8dev.12/debian/patches/series 2016-11-16 17:41:36.000000000 +1100
@@ -1,3 +1,5 @@
lynxcfg.patch
aboutlynx.patch
use-dpkg-buildflags.patch
+CVE-2016-9179-1.patch
+CVE-2016-9179-2.patch
diff -Nru lynx-cur-2.8.8dev.12/debian/source/format lynx-cur-2.8.8dev.12/debian/source/format
--- lynx-cur-2.8.8dev.12/debian/source/format 1970-01-01 10:00:00.000000000 +1000
+++ lynx-cur-2.8.8dev.12/debian/source/format 2016-11-16 17:53:26.000000000 +1100
@@ -0,0 +1 @@
+3.0 (quilt)
--
Brian May <bam@debian.org>
Reply to: