From a5cac25efd6ee857bab0b933ca5f7bbe1ede5074 Mon Sep 17 00:00:00 2001 From: Guy Harris Date: Sun, 7 May 2023 16:53:39 -0700 Subject: compiler: parse port numbers in ranges using the standard number parser. Run them through the same code that's used for numbers in the lexical analyzer, for consistency. --- gencode.c | 280 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- scanner.l | 169 ++++++++++++++++++++++++++++--------- 2 files changed, 404 insertions(+), 45 deletions(-) diff --git a/gencode.c b/gencode.c index 138ed3ac..aefa9fc9 100644 --- a/gencode.c +++ b/gencode.c @@ -6682,6 +6682,277 @@ gen_proto(compiler_state_t *cstate, bpf_u_int32 v, int proto, int dir) /*NOTREACHED*/ } +/* + * Convert a non-numeric name to a port number. + */ +static int +nametoport(compiler_state_t *cstate, const char *name, int ipproto) +{ + struct addrinfo hints, *res, *ai; + int error; + struct sockaddr_in *in4; +#ifdef INET6 + struct sockaddr_in6 *in6; +#endif + int port = -1; + + /* + * We check for both TCP and UDP in case there are + * ambiguous entries. + */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = (ipproto == IPPROTO_TCP) ? SOCK_STREAM : SOCK_DGRAM; + hints.ai_protocol = ipproto; + error = getaddrinfo(NULL, name, &hints, &res); + if (error != 0) { + switch (error) { + + case EAI_NONAME: + case EAI_SERVICE: + /* + * No such port. Just return -1. + */ + break; + +#ifdef EAI_SYSTEM + case EAI_SYSTEM: + /* + * We don't use strerror() because it's not + * guaranteed to be thread-safe on all platforms + * (probably because it might use a non-thread-local + * buffer into which to format an error message + * if the error code isn't one for which it has + * a canned string; three cheers for C string + * handling). + */ + bpf_set_error(cstate, "getaddrinfo(\"%s\" fails with system error: %d", + name, errno); + port = -2; /* a real error */ + break; +#endif + + default: + /* + * This is a real error, not just "there's + * no such service name". + * + * We don't use gai_strerror() because it's not + * guaranteed to be thread-safe on all platforms + * (probably because it might use a non-thread-local + * buffer into which to format an error message + * if the error code isn't one for which it has + * a canned string; three cheers for C string + * handling). + */ + bpf_set_error(cstate, "getaddrinfo(\"%s\") fails with error: %d", + name, error); + port = -2; /* a real error */ + break; + } + } else { + /* + * OK, we found it. Did it find anything? + */ + for (ai = res; ai != NULL; ai = ai->ai_next) { + /* + * Does it have an address? + */ + if (ai->ai_addr != NULL) { + /* + * Yes. Get a port number; we're done. + */ + if (ai->ai_addr->sa_family == AF_INET) { + in4 = (struct sockaddr_in *)ai->ai_addr; + port = ntohs(in4->sin_port); + break; + } +#ifdef INET6 + if (ai->ai_addr->sa_family == AF_INET6) { + in6 = (struct sockaddr_in6 *)ai->ai_addr; + port = ntohs(in6->sin6_port); + break; + } +#endif + } + } + freeaddrinfo(res); + } + return port; +} + +/* + * Convert a string to a port number. + */ +static bpf_u_int32 +stringtoport(compiler_state_t *cstate, const char *string, size_t string_size, + int *proto) +{ + stoulen_ret ret; + char *cpy; + bpf_u_int32 val; + int tcp_port = -1; + int udp_port = -1; + + /* + * See if it's a number. + */ + ret = stoulen(string, string_size, &val, cstate); + switch (ret) { + + case STOULEN_OK: + /* Unknown port type - it's just a numbrer. */ + *proto = PROTO_UNDEF; + break; + + case STOULEN_NOT_OCTAL_NUMBER: + case STOULEN_NOT_HEX_NUMBER: + case STOULEN_NOT_DECIMAL_NUMBER: + /* + * Not a valid number; try looking it up as a port. + */ + cpy = malloc(string_size + 1); /* +1 for terminating '\0' */ + memcpy(cpy, string, string_size); + cpy[string_size] = '\0'; + tcp_port = nametoport(cstate, cpy, IPPROTO_TCP); + if (tcp_port == -2) { + /* + * We got a hard error; the error string has + * already been set. + */ + free(cpy); + longjmp(cstate->top_ctx, 1); + /*NOTREACHED*/ + } + udp_port = nametoport(cstate, cpy, IPPROTO_UDP); + if (udp_port == -2) { + /* + * We got a hard error; the error string has + * already been set. + */ + free(cpy); + longjmp(cstate->top_ctx, 1); + /*NOTREACHED*/ + } + + /* + * We need to check /etc/services for ambiguous entries. + * If we find an ambiguous entry, and it has the + * same port number, change the proto to PROTO_UNDEF + * so both TCP and UDP will be checked. + */ + if (tcp_port >= 0) { + val = (bpf_u_int32)tcp_port; + *proto = IPPROTO_TCP; + if (udp_port >= 0) { + if (udp_port == tcp_port) + *proto = PROTO_UNDEF; +#ifdef notdef + else + /* Can't handle ambiguous names that refer + to different port numbers. */ + warning("ambiguous port %s in /etc/services", + cpy); +#endif + } + free(cpy); + break; + } + if (udp_port >= 0) { + val = (bpf_u_int32)udp_port; + *proto = IPPROTO_UDP; + free(cpy); + break; + } +#if defined(ultrix) || defined(__osf__) + /* Special hack in case NFS isn't in /etc/services */ + if (strcmp(cpy, "nfs") == 0) { + val = 2049; + *proto = PROTO_UNDEF; + free(cpy); + break; + } +#endif + bpf_set_error(cstate, "'%s' is not a valid port", cpy); + free(cpy); + longjmp(cstate->top_ctx, 1); + /*NOTREACHED*/ + + case STOULEN_ERROR: + /* Error already set. */ + longjmp(cstate->top_ctx, 1); + /*NOTREACHED*/ + + default: + /* Should not happen */ + bpf_set_error(cstate, "stoulen returned %d - this should not happen", ret); + longjmp(cstate->top_ctx, 1); + /*NOTREACHED*/ + } + return (val); +} + +/* + * Convert a string in the form PPP-PPP, which correspond to ports, to + * a starting and ending port in a port range. + */ +static void +stringtoportrange(compiler_state_t *cstate, const char *string, + bpf_u_int32 *port1, bpf_u_int32 *port2, int *proto) +{ + char *hyphen_off; + const char *first, *second; + size_t first_size, second_size; + int save_proto; + + if ((hyphen_off = strchr(string, '-')) == NULL) + bpf_error(cstate, "port range '%s' contains no hyphen", string); + + /* + * Make sure there are no other hyphens. + * + * XXX - we support named ports, but there are some port names + * in /etc/services that include hyphens, so this would rule + * that out. + */ + if (strchr(hyphen_off + 1, '-') != NULL) + bpf_error(cstate, "port range '%s' contains more than one hyphen", + string); + + /* + * Get the length of the first port. + */ + first = string; + first_size = hyphen_off - string; + if (first_size == 0) { + /* Range of "-port", which we don't support. */ + bpf_error(cstate, "port range '%s' has no starting port", string); + } + + /* + * Try to convert it to a port. + */ + *port1 = stringtoport(cstate, first, first_size, proto); + save_proto = *proto; + + /* + * Get the length of the second port. + */ + second = hyphen_off + 1; + second_size = strlen(second); + if (second_size == 0) { + /* Range of "port-", which we don't support. */ + bpf_error(cstate, "port range '%s' has no ending port", string); + } + + /* + * Try to convert it to a port. + */ + *port2 = stringtoport(cstate, second, second_size, proto); + if (*proto != save_proto) + *proto = PROTO_UNDEF; +} + struct block * gen_scode(compiler_state_t *cstate, const char *name, struct qual q) { @@ -6699,7 +6970,7 @@ gen_scode(compiler_state_t *cstate, const char *name, struct qual q) #endif /*INET6*/ struct block *b, *tmp; int port, real_proto; - int port1, port2; + bpf_u_int32 port1, port2; /* * Catch errors reported by us and routines below us, and return NULL @@ -6899,8 +7170,7 @@ gen_scode(compiler_state_t *cstate, const char *name, struct qual q) if (proto != Q_DEFAULT && proto != Q_UDP && proto != Q_TCP && proto != Q_SCTP) bpf_error(cstate, "illegal qualifier of 'portrange'"); - if (pcap_nametoportrange(name, &port1, &port2, &real_proto) == 0) - bpf_error(cstate, "unknown port in range '%s'", name); + stringtoportrange(cstate, name, &port1, &port2, &real_proto); if (proto == Q_UDP) { if (real_proto == IPPROTO_TCP) bpf_error(cstate, "port in range '%s' is tcp", name); @@ -6928,12 +7198,8 @@ gen_scode(compiler_state_t *cstate, const char *name, struct qual q) /* override PROTO_UNDEF */ real_proto = IPPROTO_SCTP; } - if (port1 < 0) - bpf_error(cstate, "illegal port number %d < 0", port1); if (port1 > 65535) bpf_error(cstate, "illegal port number %d > 65535", port1); - if (port2 < 0) - bpf_error(cstate, "illegal port number %d < 0", port2); if (port2 > 65535) bpf_error(cstate, "illegal port number %d > 65535", port2); diff --git a/scanner.l b/scanner.l index 85fe395a..c20637b2 100644 --- a/scanner.l +++ b/scanner.l @@ -32,6 +32,26 @@ #include "grammar.h" #include "diag-control.h" + +/* + * Convert string to 32-bit unsigned integer; the string starts at + * string and is string_len bytes long. + * + * On success, sets *val to the value and returns 1. + * On failure, sets the BPF error string and returns 0. + * + * Also used in gencode.c + */ +typedef enum { + STOULEN_OK, + STOULEN_NOT_HEX_NUMBER, + STOULEN_NOT_OCTAL_NUMBER, + STOULEN_NOT_DECIMAL_NUMBER, + STOULEN_ERROR +} stoulen_ret; + +stoulen_ret stoulen(const char *string, size_t stringlen, bpf_u_int32 *val, + compiler_state_t *cstate); } /* @@ -149,7 +169,7 @@ void pcap_set_column(int, yyscan_t); #include "os-proto.h" #endif -static int stou(char *, YYSTYPE *, compiler_state_t *); +static int stou(const char *, YYSTYPE *, compiler_state_t *); /* * Disable diagnostics in the code generated by Flex. @@ -490,27 +510,20 @@ tcp-cwr { yylval->h = 0x80; return NUM; } */ DIAG_ON_FLEX -/* - * Convert string to 32-bit unsigned integer. Just like atoi(), but checks for - * preceding 0x or 0 and uses hex or octal instead of decimal. - * - * On success, sets yylval->h to the value and returns NUM. - * On failure, sets the BPF error string and returns LEX_ERROR, to force - * the parse to stop. - */ -static int -stou(char *yytext_arg, YYSTYPE *yylval_arg, compiler_state_t *yyextra_arg) +stoulen_ret +stoulen(const char *string, size_t string_len, bpf_u_int32 *val, + compiler_state_t *cstate) { bpf_u_int32 n = 0; unsigned int digit; - char *s = yytext_arg; + const char *s = string; /* - * yytext_arg is guaranteed either to be a string of decimal digits + * string is guaranteed either to be a string of decimal digits * or 0[xX] followed by a string of hex digits. */ - if (*s == '0') { - if (s[1] == 'x' || s[1] == 'X') { + if (string_len >= 1 && *s == '0') { + if (string_len >= 2 && (s[1] == 'x' || s[1] == 'X')) { /* * Begins with 0x or 0X, so hex. * Guaranteed to be all hex digits following the @@ -518,13 +531,25 @@ stou(char *yytext_arg, YYSTYPE *yylval_arg, compiler_state_t *yyextra_arg) * A-F. */ s += 2; /* skip the prefix */ - while ((digit = *s++) != '\0') { + string_len -= 2; + while (string_len != 0) { + digit = *s++; + string_len--; if (digit >= '0' && digit <= '9') digit = digit - '0'; else if (digit >= 'a' && digit <= 'f') digit = digit - 'a' + 10; - else + else if (digit >= 'A' && digit <= 'F') digit = digit - 'A' + 10; + else { + /* + * Not a valid hex number. + * Don't treat this as an error, + * in case the caller wants to + * interpret it as something else. + */ + return STOULEN_NOT_HEX_NUMBER; + } /* * Check for overflow. @@ -536,10 +561,10 @@ stou(char *yytext_arg, YYSTYPE *yylval_arg, compiler_state_t *yyextra_arg) * add 4 more; that won't fit * in 32 bits. */ - bpf_set_error(yyextra_arg, - "number %s overflows 32 bits", - yytext_arg); - return LEX_ERROR; + bpf_set_error(cstate, + "number %.*s overflows 32 bits", + (int)string_len, string); + return STOULEN_ERROR; } n = (n << 4) + digit; } @@ -551,14 +576,20 @@ stou(char *yytext_arg, YYSTYPE *yylval_arg, compiler_state_t *yyextra_arg) * report an error. */ s += 1; - while ((digit = *s++) != '\0') { + string_len -= 1; + while (string_len != 0) { + digit = *s++; + string_len--; if (digit >= '0' && digit <= '7') digit = digit - '0'; else { - bpf_set_error(yyextra_arg, - "number %s contains non-octal digit", - yytext_arg); - return LEX_ERROR; + /* + * Not a valid octal number. + * Don't treat this as an error, + * in case the caller wants to + * interpret it as something else. + */ + return STOULEN_NOT_OCTAL_NUMBER; } if (n > 03777777777U) { /* @@ -567,10 +598,10 @@ stou(char *yytext_arg, YYSTYPE *yylval_arg, compiler_state_t *yyextra_arg) * 3 more; that won't fit in * 32 bits. */ - bpf_set_error(yyextra_arg, - "number %s overflows 32 bits", - yytext_arg); - return LEX_ERROR; + bpf_set_error(cstate, + "number %.*s overflows 32 bits", + (int)string_len, string); + return STOULEN_ERROR; } n = (n << 3) + digit; } @@ -579,21 +610,83 @@ stou(char *yytext_arg, YYSTYPE *yylval_arg, compiler_state_t *yyextra_arg) /* * Decimal. */ - while ((digit = *s++) != '\0') { - digit = digit - '0'; + while (string_len != 0) { + digit = *s++; + string_len--; + if (digit >= '0' && digit <= '9') + digit = digit - '0'; + else { + /* + * Not a valid decimal number. + * Don't treat this as an error, + * in case the caller wants to + * interpret it as something else. + */ + return STOULEN_NOT_DECIMAL_NUMBER; + } #define CUTOFF_DEC (0xFFFFFFFFU / 10U) #define CUTLIM_DEC (0xFFFFFFFFU % 10U) if (n > CUTOFF_DEC || (n == CUTOFF_DEC && digit > CUTLIM_DEC)) { - bpf_set_error(yyextra_arg, - "number %s overflows 32 bits", - yytext_arg); - return LEX_ERROR; + /* + * Adding that digit will result in a + * number that won't fit in 32 bits. + */ + bpf_set_error(cstate, + "number %.*s overflows 32 bits", + (int)string_len, string); + return STOULEN_ERROR; } n = (n * 10) + digit; } } - yylval_arg->h = n; - return NUM; + *val = n; + return STOULEN_OK; +} + +/* + * Convert string to 32-bit unsigned integer. Just like atoi(), but checks for + * preceding 0x or 0 and uses hex or octal instead of decimal. + * + * On success, sets yylval->h to the value and returns NUM. + * On failure, sets the BPF error string and returns LEX_ERROR, to force + * the parse to stop. + */ +static int +stou(const char *yytext_arg, YYSTYPE *yylval_arg, compiler_state_t *yyextra_arg) +{ + stoulen_ret ret; + + ret = stoulen(yytext_arg, strlen(yytext_arg), &yylval_arg->h, + yyextra_arg); + switch (ret) { + + case STOULEN_OK: + return NUM; + + case STOULEN_NOT_OCTAL_NUMBER: + bpf_set_error(yyextra_arg, "number %s contains non-octal digit", + yytext_arg); + return LEX_ERROR; + + case STOULEN_NOT_HEX_NUMBER: + bpf_set_error(yyextra_arg, "number %s contains non-hex digit", + yytext_arg); + return LEX_ERROR; + + case STOULEN_NOT_DECIMAL_NUMBER: + bpf_set_error(yyextra_arg, "number %s contains non-decimal digit", + yytext_arg); + return LEX_ERROR; + + case STOULEN_ERROR: + /* Error already set. */ + return LEX_ERROR; + + default: + /* Should not happen */ + bpf_set_error(yyextra_arg, "stoulen returned %d", ret); + return LEX_ERROR; + } } -- cgit v1.2.1