Repository: xroche/httrack (2026-07-16T16:59:20Z)

Issue:
An out-of-range -P proxy port parses to a garbage port instead of being rejected
`hts_parse_proxy` reads the port with `sscanf(a, "%d", &p)`. A value past `INT_MAX` is signed-overflow UB, and in practice yields a garbage port rather than being rejected:

```
$ httrack -#test=proxyurl 'http://host:99999999999999'
name=http://host port=276447231 host=host kind=http
```

The result is also inconsistent, because whether `sscanf` fails or silently wraps depends on the value:

```
$ httrack -#test=proxyurl 'http://host:4294967296'
name=http://host port=8080 host=host kind=http     <- sscanf fails, falls back to the default
```

A port is `*DIGIT` per RFC 3986 but has to fit 1..65535 to mean anything. Parsing it with `strtol` and range-checking (out of range falls back to `proxy_default_port`, as an unparsable port already does) would make both cases behave the same.

Low severity: it needs a malformed `-P` argument, and the damage is a connect to a nonsense port. Found while reviewing #599; not caused by it, and present well before the SOCKS5 work.



Discussion:

Patch:
diff --git a/src/htslib.c b/src/htslib.c
index 55899b264..090d18a35 100644
--- a/src/htslib.c
+++ b/src/htslib.c
@@ -65,6 +65,7 @@ Please visit our Website: http://www.httrack.com
 #endif /* _WIN32 */
 #include <stdarg.h>
 
+#include <ctype.h>
 #include <string.h>
 #include <time.h>
 #include <stdarg.h>
@@ -3747,6 +3748,20 @@ static int proxy_default_port(const char *arg) {
   return hts_proxy_is_socks(arg) ? 1080 : 8080;
 }
 
+// port "a" of -P argument "arg": digits fitting TCP's 1..65535, else the scheme
+// default. Not sscanf("%d"): past INT_MAX it wraps to a garbage port (#602)
+static int parse_proxy_port(const char *a, const char *arg) {
+  char *end;
+  long p;
+
+  if (!isdigit((unsigned char) *a)) // strtol would eat a sign or leading space
+    return proxy_default_port(arg);
+  p = strtol(a, &end, 10);
+  if (*end != '\0' || p < 1 || p > 65535) // ERANGE lands out of range too
+    return proxy_default_port(arg);
+  return (int) p;
+}
+
 void hts_parse_proxy(const char *arg, char *name, size_t name_size, int *port) {
   const char *authority = strstr(arg, "://");
   const char *a;
@@ -3762,10 +3777,7 @@ void hts_parse_proxy(const char *arg, char *name, size_t name_size, int *port) {
   while (a > authority && a[-1] != ':' && a[-1] != '@' && a[-1] != ']')
     a--;
   if (a > authority && a[-1] == ':') {
-    int p = -1;
-
-    sscanf(a, "%d", &p);
-    *port = (p > 0) ? p : proxy_default_port(arg);
+    *port = parse_proxy_port(a, arg);
     namelen = (size_t) (a - 1 - arg);
   } else {
     *port = proxy_default_port(arg);


Test patch:
diff --git a/tests/01_engine-proxyurl.test b/tests/01_engine-proxyurl.test
index 41a5329aa..2548a23dc 100755
--- a/tests/01_engine-proxyurl.test
+++ b/tests/01_engine-proxyurl.test
@@ -71,3 +71,23 @@ p 'http://a]b:c@host' 'name=http://a]b:c@host port=8080 host=host kind=http'
 
 # a lone ':' must not underflow the backward scan
 p ':' 'name= port=8080 host= kind=http'
+
+# a port is *DIGIT in 1..65535, else the scheme default: sscanf("%d") used to
+# wrap past INT_MAX and hand back a garbage port (#602)
+p 'http://host:65535' 'name=http://host port=65535 host=host kind=http'
+p 'http://host:1' 'name=http://host port=1 host=host kind=http'
+p 'http://host:65536' 'name=http://host port=8080 host=host kind=http'
+p 'http://host:2147483648' 'name=http://host port=8080 host=host kind=http'
+p 'http://host:4294967296' 'name=http://host port=8080 host=host kind=http'
+p 'http://host:99999999999999' 'name=http://host port=8080 host=host kind=http'
+# discriminating: this one wrapped to a plausible 80, so range-checking the
+# sscanf result instead of rejecting the overflow still passes everything above
+p 'http://host:4294967376' 'name=http://host port=8080 host=host kind=http'
+p 'http://host:999999999999999999999999999999' 'name=http://host port=8080 host=host kind=http'
+p 'http://host:0' 'name=http://host port=8080 host=host kind=http'
+p 'http://host:-1' 'name=http://host port=8080 host=host kind=http'
+p 'http://host:80x' 'name=http://host port=8080 host=host kind=http'
+p 'http://host: 80' 'name=http://host port=8080 host=host kind=http'
+p 'http://host:' 'name=http://host port=8080 host=host kind=http'
+# the fallback is the scheme's own default, not a hardcoded 8080
+p 'socks5://host:99999999999999' 'name=socks5://host port=1080 host=host kind=socks'