[5G-NAS] Improve QoS Rules parsing

The older version of the code was wrong (or at least not exactly
correct) in many (corner) cases.

* Split the parsing of Packet Filter List into its own helper function
  to simplify the code
* Improve error logging to provide more info on which QoS rule failed.
* Add some extra logic checking match between 'Length of QoS rule' and
  existance of m+1 and m+2 bytes.
* Correct logic checking expected/unexpected presence of m+1 and m+2
  octets based on Rule Operation Code according to specs.
This commit is contained in:
Pau Espin Pedrol
2025-08-22 16:48:13 +02:00
committed by Sukchan Lee
parent 1bdbaa4ef2
commit b11350f969

View File

@@ -805,14 +805,214 @@ int ogs_nas_build_qos_rules(ogs_nas_qos_rules_t *rules,
return OGS_OK;
}
/* Parse "Packet filter list", 3GPP TS 24.501 Figure 9.11.4.13.3 and Figure 9.11.4.13. */
static int parse_qos_rules_packet_filter_list(ogs_nas_qos_rule_t *rule, const uint8_t *buffer, uint16_t length) {
uint16_t size = 0;
int i, j, len = 0;
for (i = 0; i < rule->num_of_packet_filter && i < OGS_MAX_NUM_OF_FLOW_IN_GTP; i++) {
if (size+sizeof(rule->pf[i].flags) > length) {
ogs_error("PF[%d] Overflow: size[%d] length[%d]", i, size, length);
goto cleanup;
}
memcpy(&rule->pf[i].flags, buffer+size, sizeof(rule->pf[i].flags));
size += sizeof(rule->pf[i].flags);
if (rule->code ==
OGS_NAS_QOS_CODE_MODIFY_EXISTING_QOS_RULE_AND_DELETE_PACKET_FILTERS)
continue;
if (size+sizeof(rule->pf[i].content.length) > length) {
ogs_error("PF[%d] Overflow: size[%d] length[%d]", i, size, length);
goto cleanup;
}
memcpy(&rule->pf[i].content.length, buffer+size,
sizeof(rule->pf[i].content.length));
size += sizeof(rule->pf[i].content.length);
j = 0; len = 0;
while(len < rule->pf[i].content.length) {
if (size+len+
sizeof(rule->pf[i].content.component[j].type) > length) {
ogs_error("PF[%d] Overflow: size[%d] len[%d] length[%d]",
i, size, len, length);
goto cleanup;
}
memcpy(&rule->pf[i].content.component[j].type,
buffer+size+len,
sizeof(rule->pf[i].content.component[j].type));
len += sizeof(rule->pf[i].content.component[j].type);
switch(rule->pf[i].content.component[j].type) {
case OGS_PACKET_FILTER_PROTOCOL_IDENTIFIER_NEXT_HEADER_TYPE:
if (size+len+
sizeof(rule->pf[i].content.component[j].proto) >
length) {
ogs_error("PF[%d] Overflow: size[%d] len[%d] length[%d]",
i, size, len, length);
goto cleanup;
}
memcpy(&rule->pf[i].content.component[j].proto,
buffer+size+len,
sizeof(rule->pf[i].content.component[j].proto));
len += sizeof(rule->pf[i].content.component[j].proto);
break;
case OGS_PACKET_FILTER_IPV4_REMOTE_ADDRESS_TYPE:
case OGS_PACKET_FILTER_IPV4_LOCAL_ADDRESS_TYPE:
if (size+len+
sizeof(rule->pf[i].content.component[j].ipv4.addr) >
length) {
ogs_error("PF[%d] Overflow: size[%d] len[%d] length[%d]",
i, size, len, length);
goto cleanup;
}
memcpy(&rule->pf[i].content.component[j].ipv4.addr,
buffer+size+len,
sizeof(rule->pf[i].content.component[j].ipv4.addr));
len += sizeof(rule->pf[i].content.component[j].ipv4.addr);
if (size+len+
sizeof(rule->pf[i].content.component[j].ipv4.mask) >
length) {
ogs_error("PF[%d] Overflow: size[%d] len[%d] length[%d]",
i, size, len, length);
goto cleanup;
}
memcpy(&rule->pf[i].content.component[j].ipv4.mask,
buffer+size+len,
sizeof(rule->pf[i].content.component[j].ipv4.mask));
len += sizeof(rule->pf[i].content.component[j].ipv4.mask);
break;
case OGS_PACKET_FILTER_IPV6_LOCAL_ADDRESS_PREFIX_LENGTH_TYPE:
case OGS_PACKET_FILTER_IPV6_REMOTE_ADDRESS_PREFIX_LENGTH_TYPE:
if (size+len+
sizeof(rule->pf[i].content.component[j].ipv6.addr) >
length) {
ogs_error("PF[%d] Overflow: size[%d] len[%d] length[%d]",
i, size, len, length);
goto cleanup;
}
memcpy(&rule->pf[i].content.component[j].ipv6.addr,
buffer+size+len,
sizeof(rule->pf[i].content.component[j].ipv6.addr));
len += sizeof(rule->pf[i].content.component[j].ipv6.addr);
if (size+len+
sizeof(
rule->pf[i].content.component[j].ipv6.prefixlen) >
length) {
ogs_error("PF[%d] Overflow: size[%d] len[%d] length[%d]",
i, size, len, length);
goto cleanup;
}
memcpy(&rule->pf[i].content.component[j].ipv6.prefixlen,
buffer+size+len,
sizeof(
rule->pf[i].content.component[j].ipv6.prefixlen));
len += sizeof(
rule->pf[i].content.component[j].ipv6.prefixlen);
break;
case OGS_PACKET_FILTER_IPV6_LOCAL_ADDRESS_TYPE:
case OGS_PACKET_FILTER_IPV6_REMOTE_ADDRESS_TYPE:
if (size+len+
sizeof(
rule->pf[i].content.component[j].ipv6_mask.addr) >
length) {
ogs_error("PF[%d] Overflow: size[%d] len[%d] length[%d]",
i, size, len, length);
goto cleanup;
}
memcpy(&rule->pf[i].content.component[j].ipv6_mask.addr,
buffer+size+len,
sizeof(
rule->pf[i].content.component[j].ipv6_mask.addr));
len += sizeof(
rule->pf[i].content.component[j].ipv6_mask.addr);
if (size+len+
sizeof(
rule->pf[i].content.component[j].ipv6_mask.mask) >
length) {
ogs_error("PF[%d] Overflow: size[%d] len[%d] length[%d]",
i, size, len, length);
goto cleanup;
}
memcpy(&rule->pf[i].content.component[j].ipv6_mask.mask,
buffer+size+len,
sizeof(
rule->pf[i].content.component[j].ipv6_mask.mask));
len += sizeof(
rule->pf[i].content.component[j].ipv6_mask.mask);
break;
case OGS_PACKET_FILTER_SINGLE_LOCAL_PORT_TYPE:
case OGS_PACKET_FILTER_SINGLE_REMOTE_PORT_TYPE:
if (size+len+
sizeof(rule->pf[i].content.component[j].port.low) >
length) {
ogs_error("PF[%d] Overflow: size[%d] len[%d] length[%d]",
i, size, len, length);
goto cleanup;
}
memcpy(&rule->pf[i].content.component[j].port.low,
buffer+size+len,
sizeof(rule->pf[i].content.component[j].port.low));
rule->pf[i].content.component[j].port.low =
be16toh(rule->pf[i].content.component[j].port.low);
len += sizeof(rule->pf[i].content.component[j].port.low);
break;
case OGS_PACKET_FILTER_LOCAL_PORT_RANGE_TYPE:
case OGS_PACKET_FILTER_REMOTE_PORT_RANGE_TYPE:
if (size+len+
sizeof(rule->pf[i].content.component[j].port.low) >
length) {
ogs_error("PF[%d] Overflow: size[%d] len[%d] length[%d]",
i, size, len, length);
goto cleanup;
}
memcpy(&rule->pf[i].content.component[j].port.low,
buffer+size+len,
sizeof(rule->pf[i].content.component[j].port.low));
rule->pf[i].content.component[j].port.low =
be16toh(rule->pf[i].content.component[j].port.low);
len += sizeof(rule->pf[i].content.component[j].port.low);
if (size+len+
sizeof(rule->pf[i].content.component[j].port.high) >
length) {
ogs_error("PF[%d] Overflow: size[%d] len[%d] length[%d]",
i, size, len, length);
goto cleanup;
}
memcpy(&rule->pf[i].content.component[j].port.high,
buffer+size+len,
sizeof(rule->pf[i].content.component[j].port.high));
rule->pf[i].content.component[j].port.high =
be16toh(rule->pf[i].content.component[j].port.high);
len += sizeof(rule->pf[i].content.component[j].port.high);
break;
default:
ogs_error("PF[%d] Unknown Packet Filter Type(%d)",
i, rule->pf[i].content.component[j].type);
goto cleanup;
}
j++;
}
rule->pf[i].content.num_of_component = j;
size += len;
}
return size;
cleanup:
return -1;
}
/* Parse "QoS rules", 3GPP TS 24.501 Figure 9.11.4.13.1. */
int ogs_nas_parse_qos_rules(
ogs_nas_qos_rule_t *rule, ogs_nas_qos_rules_t *rules)
{
ogs_nas_qos_rule_t *first = rule;
char *buffer;
uint16_t length, size = 0;
int i, j, len = 0;
uint8_t *buffer;
uint16_t length, size;
int rc;
ogs_assert(rule);
ogs_assert(rules);
@@ -828,9 +1028,10 @@ int ogs_nas_parse_qos_rules(
length = rules->length;
buffer = rules->buffer;
size = 0;
while (size < length) {
bool have_octet_m1, have_octet_m2;
memset(rule, 0, sizeof(*rule));
if (size+sizeof(rule->identifier) > length) {
@@ -841,244 +1042,106 @@ int ogs_nas_parse_qos_rules(
size += sizeof(rule->identifier);
if (size+sizeof(rule->length) > length) {
ogs_error("Overflow : size[%d] length[%d]", size, length);
ogs_error("RuleId[%u] Overflow: size[%d] length[%d]", rule->identifier, size, length);
goto cleanup;
}
memcpy(&rule->length, buffer+size, sizeof(rule->length));
rule->length = be16toh(rule->length);
size += sizeof(rule->length);
if (rule->length == 0) {
ogs_error("RuleId[%u] Wrong 'Length of QoS rule' (0)", rule->identifier);
goto cleanup;
}
if (size+sizeof(rule->flags) > length) {
ogs_error("Overflow : size[%d] length[%d]", size, length);
ogs_error("RuleId[%u] Overflow: size[%d] length[%d]", rule->identifier, size, length);
goto cleanup;
}
memcpy(&rule->flags, buffer+size, sizeof(rule->flags));
size += sizeof(rule->flags);
if (rule->code == 0 || rule->code == 7) { /* Reserved */
ogs_error("Reserved Rule Code [%d]", rule->code);
ogs_error("RuleId[%u] Reserved Rule Code [%d]", rule->identifier, rule->code);
goto cleanup;
}
if (rule->code == OGS_NAS_QOS_CODE_DELETE_EXISTING_QOS_RULE ||
rule->code == OGS_NAS_QOS_CODE_MODIFY_EXISTING_QOS_RULE_WITHOUT_MODIFYING_PACKET_FILTERS) {
if (rule->num_of_packet_filter != 0) {
ogs_error("Invalue QoS rule code[%d] "
"and number of packet filter[%d]",
rule->code, rule->num_of_packet_filter);
ogs_error("RuleId[%u] Invalid QoS rule code[%d] and number of packet filter[%d]",
rule->identifier, rule->code, rule->num_of_packet_filter);
rule->num_of_packet_filter = 0;
goto cleanup;
}
}
for (i = 0; i < rule->num_of_packet_filter &&
i < OGS_MAX_NUM_OF_FLOW_IN_GTP; i++) {
if (size+sizeof(rule->pf[i].flags) > length) {
ogs_error("Overflow : size[%d] length[%d]", size, length);
rc = parse_qos_rules_packet_filter_list(rule, buffer + size, rule->length - 1);
if (rc < 0)
goto cleanup;
size += rc;
if (rule->length == (sizeof(rule->flags) + rc)) {
have_octet_m1 = false;
have_octet_m2 = false;
} else if (rule->length == (sizeof(rule->flags) + rc + sizeof(rule->precedence))) {
have_octet_m1 = true;
have_octet_m2 = false;
} else if (rule->length == (sizeof(rule->flags) + rc + sizeof(rule->precedence) + sizeof(rule->flow.flags))) {
have_octet_m1 = true;
have_octet_m2 = true;
} else {
ogs_error("RuleId[%u] 'Length of QoS rule' (%d) doesn't match parsed length (%zu..%zu)",
rule->identifier, rule->length,
sizeof(rule->flags) + rc,
sizeof(rule->flags) + rc + sizeof(rule->precedence) + sizeof(rule->flow.flags));
goto cleanup;
}
memcpy(&rule->pf[i].flags, buffer+size, sizeof(rule->pf[i].flags));
size += sizeof(rule->pf[i].flags);
if (rule->code ==
OGS_NAS_QOS_CODE_MODIFY_EXISTING_QOS_RULE_AND_DELETE_PACKET_FILTERS)
continue;
if (size+sizeof(rule->pf[i].content.length) > length) {
ogs_error("Overflow : size[%d] length[%d]", size, length);
goto cleanup;
}
memcpy(&rule->pf[i].content.length, buffer+size,
sizeof(rule->pf[i].content.length));
size += sizeof(rule->pf[i].content.length);
j = 0; len = 0;
while(len < rule->pf[i].content.length) {
if (size+len+
sizeof(rule->pf[i].content.component[j].type) > length) {
ogs_error("Overflow : size[%d] len[%d] length[%d]",
size, len, length);
goto cleanup;
}
memcpy(&rule->pf[i].content.component[j].type,
buffer+size+len,
sizeof(rule->pf[i].content.component[j].type));
len += sizeof(rule->pf[i].content.component[j].type);
switch(rule->pf[i].content.component[j].type) {
case OGS_PACKET_FILTER_PROTOCOL_IDENTIFIER_NEXT_HEADER_TYPE:
if (size+len+
sizeof(rule->pf[i].content.component[j].proto) >
length) {
ogs_error("Overflow : size[%d] len[%d] length[%d]",
size, len, length);
goto cleanup;
}
memcpy(&rule->pf[i].content.component[j].proto,
buffer+size+len,
sizeof(rule->pf[i].content.component[j].proto));
len += sizeof(rule->pf[i].content.component[j].proto);
break;
case OGS_PACKET_FILTER_IPV4_REMOTE_ADDRESS_TYPE:
case OGS_PACKET_FILTER_IPV4_LOCAL_ADDRESS_TYPE:
if (size+len+
sizeof(rule->pf[i].content.component[j].ipv4.addr) >
length) {
ogs_error("Overflow : size[%d] len[%d] length[%d]",
size, len, length);
goto cleanup;
}
memcpy(&rule->pf[i].content.component[j].ipv4.addr,
buffer+size+len,
sizeof(rule->pf[i].content.component[j].ipv4.addr));
len += sizeof(rule->pf[i].content.component[j].ipv4.addr);
if (size+len+
sizeof(rule->pf[i].content.component[j].ipv4.mask) >
length) {
ogs_error("Overflow : size[%d] len[%d] length[%d]",
size, len, length);
goto cleanup;
}
memcpy(&rule->pf[i].content.component[j].ipv4.mask,
buffer+size+len,
sizeof(rule->pf[i].content.component[j].ipv4.mask));
len += sizeof(rule->pf[i].content.component[j].ipv4.mask);
break;
case OGS_PACKET_FILTER_IPV6_LOCAL_ADDRESS_PREFIX_LENGTH_TYPE:
case OGS_PACKET_FILTER_IPV6_REMOTE_ADDRESS_PREFIX_LENGTH_TYPE:
if (size+len+
sizeof(rule->pf[i].content.component[j].ipv6.addr) >
length) {
ogs_error("Overflow : size[%d] len[%d] length[%d]",
size, len, length);
goto cleanup;
}
memcpy(&rule->pf[i].content.component[j].ipv6.addr,
buffer+size+len,
sizeof(rule->pf[i].content.component[j].ipv6.addr));
len += sizeof(rule->pf[i].content.component[j].ipv6.addr);
if (size+len+
sizeof(
rule->pf[i].content.component[j].ipv6.prefixlen) >
length) {
ogs_error("Overflow : size[%d] len[%d] length[%d]",
size, len, length);
goto cleanup;
}
memcpy(&rule->pf[i].content.component[j].ipv6.prefixlen,
buffer+size+len,
sizeof(
rule->pf[i].content.component[j].ipv6.prefixlen));
len += sizeof(
rule->pf[i].content.component[j].ipv6.prefixlen);
break;
case OGS_PACKET_FILTER_IPV6_LOCAL_ADDRESS_TYPE:
case OGS_PACKET_FILTER_IPV6_REMOTE_ADDRESS_TYPE:
if (size+len+
sizeof(
rule->pf[i].content.component[j].ipv6_mask.addr) >
length) {
ogs_error("Overflow : size[%d] len[%d] length[%d]",
size, len, length);
goto cleanup;
}
memcpy(&rule->pf[i].content.component[j].ipv6_mask.addr,
buffer+size+len,
sizeof(
rule->pf[i].content.component[j].ipv6_mask.addr));
len += sizeof(
rule->pf[i].content.component[j].ipv6_mask.addr);
if (size+len+
sizeof(
rule->pf[i].content.component[j].ipv6_mask.mask) >
length) {
ogs_error("Overflow : size[%d] len[%d] length[%d]",
size, len, length);
goto cleanup;
}
memcpy(&rule->pf[i].content.component[j].ipv6_mask.mask,
buffer+size+len,
sizeof(
rule->pf[i].content.component[j].ipv6_mask.mask));
len += sizeof(
rule->pf[i].content.component[j].ipv6_mask.mask);
break;
case OGS_PACKET_FILTER_SINGLE_LOCAL_PORT_TYPE:
case OGS_PACKET_FILTER_SINGLE_REMOTE_PORT_TYPE:
if (size+len+
sizeof(rule->pf[i].content.component[j].port.low) >
length) {
ogs_error("Overflow : size[%d] len[%d] length[%d]",
size, len, length);
goto cleanup;
}
memcpy(&rule->pf[i].content.component[j].port.low,
buffer+size+len,
sizeof(rule->pf[i].content.component[j].port.low));
rule->pf[i].content.component[j].port.low =
be16toh(rule->pf[i].content.component[j].port.low);
len += sizeof(rule->pf[i].content.component[j].port.low);
break;
case OGS_PACKET_FILTER_LOCAL_PORT_RANGE_TYPE:
case OGS_PACKET_FILTER_REMOTE_PORT_RANGE_TYPE:
if (size+len+
sizeof(rule->pf[i].content.component[j].port.low) >
length) {
ogs_error("Overflow : size[%d] len[%d] length[%d]",
size, len, length);
goto cleanup;
}
memcpy(&rule->pf[i].content.component[j].port.low,
buffer+size+len,
sizeof(rule->pf[i].content.component[j].port.low));
rule->pf[i].content.component[j].port.low =
be16toh(rule->pf[i].content.component[j].port.low);
len += sizeof(rule->pf[i].content.component[j].port.low);
if (size+len+
sizeof(rule->pf[i].content.component[j].port.high) >
length) {
ogs_error("Overflow : size[%d] len[%d] length[%d]",
size, len, length);
goto cleanup;
}
memcpy(&rule->pf[i].content.component[j].port.high,
buffer+size+len,
sizeof(rule->pf[i].content.component[j].port.high));
rule->pf[i].content.component[j].port.high =
be16toh(rule->pf[i].content.component[j].port.high);
len += sizeof(rule->pf[i].content.component[j].port.high);
break;
default:
ogs_error("Unknown Packet Filter Type(%d)",
rule->pf[i].content.component[j].type);
goto cleanup;
}
j++;
}
rule->pf[i].content.num_of_component = j;
size += len;
}
if (rule->code != OGS_NAS_QOS_CODE_DELETE_EXISTING_QOS_RULE &&
rule->code != OGS_NAS_QOS_CODE_MODIFY_EXISTING_QOS_RULE_AND_DELETE_PACKET_FILTERS &&
rule->code != OGS_NAS_QOS_CODE_MODIFY_EXISTING_QOS_RULE_WITHOUT_MODIFYING_PACKET_FILTERS) {
if (size+sizeof(rule->precedence) > length) {
ogs_error("Overflow : size[%d] length[%d]", size, length);
if (have_octet_m1) {
if ((size + sizeof(rule->precedence)) > length) {
ogs_error("RuleId[%u] Overflow m+1: size[%d] length[%d]", rule->identifier, size, length);
goto cleanup;
}
memcpy(&rule->precedence, buffer+size, sizeof(rule->precedence));
size += sizeof(rule->precedence);
if (size+sizeof(rule->flow.flags) > length) {
ogs_error("Overflow : size[%d] length[%d]", size, length);
/* 'For the "delete existing QoS rule" operation, the QoS rule precedence value field shall not
* be included.'
* This implicitly means also m+2 shall neither be present, following Table 9.11.4.13. "NOTE 1". */
if (rule->code == OGS_NAS_QOS_CODE_DELETE_EXISTING_QOS_RULE) {
ogs_error("RuleId[%u] Invalid QoS rule code[%d] and presence of Precedence octet [%u]",
rule->identifier, rule->code, rule->precedence);
goto cleanup;
}
memcpy(&rule->flow.flags, buffer+size, sizeof(rule->flow.flags));
size += sizeof(rule->flow.flags);
if (have_octet_m2) {
if ((size + sizeof(rule->flow.flags)) > length) {
ogs_error("RuleId[%u] Overflow m+2: size[%d] length[%d]", rule->identifier, size, length);
goto cleanup;
}
memcpy(&rule->flow.flags, buffer+size, sizeof(rule->flow.flags));
size += sizeof(rule->flow.flags);
/* 'For the "delete existing QoS rule" operation, the QoS rule precedence value field shall not
* be included.'
* This implicitly means also m+2 shall neither be present, following Table 9.11.4.13. "NOTE 1". */
if (rule->code == OGS_NAS_QOS_CODE_DELETE_EXISTING_QOS_RULE) {
ogs_error("RuleId[%u] Invalid QoS rule code[%d] and presence of QFI octet [%u]",
rule->identifier, rule->code, rule->flow.flags);
goto cleanup;
}
} else if (rule->code == OGS_NAS_QOS_CODE_CREATE_NEW_QOS_RULE) {
/* 'For the "create new QoS rule" operation, the QoS flow identifier value field shall
* be included.' */
ogs_error("RuleId[%u] Invalid QoS rule code[%d] without QFI octet",
rule->identifier, rule->code);
goto cleanup;
}
} else if (rule->code == OGS_NAS_QOS_CODE_CREATE_NEW_QOS_RULE) {
/* 'For the "create new QoS rule" operation, the QoS rule precedence value field shall
* be included.' */
ogs_error("RuleId[%u] Invalid QoS rule code[%d] without Precedence octet",
rule->identifier, rule->code);
goto cleanup;
}
rule++;