a1b16efb1479af46ea0ed5d6464f084479ce8490 braney Wed Sep 2 11:20:43 2026 -0700 wikiLink: returnUrlSchemeIsSafe should refuse a scheme-relative URL, refs #38172 The function allows an http URL, an https URL or a relative one. It found the scheme by looking for a colon, so "//host/path" fell through the first test and was accepted as relative, though it names another host. Refuse it, and say so in the comment. Raised in the v503 Preview II code review. diff --git src/hg/lib/wikiLink.c src/hg/lib/wikiLink.c index 3fcf8cb6f8a..19c1108a90f 100644 --- src/hg/lib/wikiLink.c +++ src/hg/lib/wikiLink.c @@ -426,35 +426,39 @@ static char *encodedHgSessionReturnUrl(char *hgsid) /* Return a CGI-encoded hgSession URL with hgsid. Free when done. */ { return wikiLinkEncodeReturnUrl(hgsid, "hgSession", ""); } /* Longest return URL we will build. hgLogin copies the decoded returnto into a 2 kB buffer * and aborts if it does not fit, and cgi-encoding can nearly triple the length on the way * there, so a page with a very long query string gives up the query string, not the trip * back. */ #define RETURN_URL_MAX 1000 static boolean returnUrlSchemeIsSafe(char *returnUrl) -/* Return TRUE unless returnUrl carries a scheme other than http or https. The scheme is the +/* Return TRUE only for an http URL, an https URL, or a path-relative URL. The scheme is the * text before the first colon, and only when that colon comes before any slash, question mark * or hash; a colon after one of those belongs to the path or the query, so the URL is relative. - * This is what keeps a javascript: or data: URL out of the href hgLogin writes. */ + * This is what keeps a javascript: or data: URL out of the href hgLogin writes. A + * scheme-relative "//host/path" is refused as well: it names another host but carries no + * colon, so it is not relative in the sense this function allows. */ { +if (startsWith("//", returnUrl)) + return FALSE; char *colon = strchr(returnUrl, ':'); if (colon == NULL) return TRUE; char *pathStart = strpbrk(returnUrl, "/?#"); if (pathStart != NULL && pathStart < colon) return TRUE; int schemeLen = colon - returnUrl; return (schemeLen == 4 && startsWithNoCase("http", returnUrl)) || (schemeLen == 5 && startsWithNoCase("https", returnUrl)); } static boolean returnUrlIsWellFormed(char *returnUrl) /* Return TRUE if returnUrl looks like a URL hgLogin can write into its page. Every CGI * parameter becomes a cart variable, so returnto holds whatever the visitor's URL said, and it * is printed into an href attribute and into a javascript location assignment. A quote, an