opnsense / core

OPNsense GUI, API and systems backend
https://opnsense.org/
BSD 2-Clause "Simplified" License
3.22k stars 718 forks source link

Fatal memory exhaustion error when updating firewall rules via REST API #7608

Open tschunk opened 1 month ago

tschunk commented 1 month ago

Important notices

Before you add a new report, we ask you kindly to acknowledge the following:

Describe the bug

On OPNsense 24.1.9 when using the REST API to manage firewall rules, updating a rule via REST API fails with a fatal error:

> POST /api/firewall/filter/set_rule/<uuid>

< HTTP/2 200
< Fatal error: Allowed memory size of 1073741824 bytes exhausted (tried to allocate 4096 bytes) in /usr/local/opnsense/mvc/app/models/OPNsense/Base/FieldTypes/BaseListField.php on line 156

This only occurs if a lot of rules are present and a rule is tried to be updated via REST API. In my tests the threshold was at about 300 rules. Creation and deletion of rules via API still works fine.

Notes:

To Reproduce

Steps to reproduce the behavior:

  1. Use REST API to create 350 firewall rules (I tested with PASS TCP 1.1.1.1 -> 2.2.2.2:80)
  2. Use REST API (or UI: Firewall -> Automation) to update one of the rules (e.g. change description)
  3. API request fails with fatal error and rule is not changed.

Or use this script:

#!/bin/bash
set -ex

firewall="https://CHANGEME"
key=CHANGEME
secret=CHANGEME

# create 300 firewall rules
for i in $(seq 300); do
  res=$(curl -ks "${firewall}/api/firewall/filter/add_rule" \
    --user "${key}:${secret}" \
    -X POST -H 'Content-Type: application/json' \
    --data-raw '{"rule":{"enabled":"1","sequence":"1","action":"pass","quick":"1","interface":"lan","direction":"in","ipprotocol":"inet","protocol":"TCP","source_net":"1.1.1.1","source_port":"","source_not":"0","destination_net":"2.2.2.2","destination_not":"0","destination_port":"80","gateway":"","log":"0","categories":"","description":"create"}}'
  )
done

# uuid of last created rule
uuid="$(echo "$res" | sed -re 's/.*"uuid":"(.+)".*/\1/')"

# try to update a rule
# will print `Fatal error: Allowed memory size of 1073741824 bytes exhausted`
curl -ksv "${firewall}/api/firewall/filter/set_rule/${uuid}" \
  --user "${key}:${secret}" \
  -X POST -H 'Content-Type: application/json' \
  --data-raw '{"rule":{"enabled":"1","sequence":"1","action":"pass","quick":"1","interface":"lan","direction":"in","ipprotocol":"inet","protocol":"TCP","source_net":"1.1.1.1","source_port":"","source_not":"0","destination_net":"2.2.2.2","destination_not":"0","destination_port":"80","gateway":"","log":"0","categories":"","description":"update"}}'

Expected behavior

Using REST API to update a firewall rule does not fail with an error and rule is correctly updated.

Describe alternatives you considered

Using the API to delete the rule and create a new rule with the required changes does still work, but this is very cumbersome.

Relevant log files

Only the above error message is returned in HTTP API call and also logged to /tmp/PHP_errors.log. I could not find any other relevant logs. I also submitted this error via System: Firmware: Reporter

Environment

OPNsense 24.1.9_4 (amd64) Tested in virtual machine (qemu)

AdSchellevis commented 1 month ago

the set and add actions are roughly the same, so the question is if you can also trigger your issue when trying to set a non-existing uuid (just remove the one created in step one and update the same uuid).

Since set is an upsert operation, when the entry doesn't exist, it should do roughly same as add in this case:

https://github.com/opnsense/core/blob/c1f51000f997ad252d9176008c33ba5ca640d511/src/opnsense/mvc/app/controllers/OPNsense/Base/ApiMutableModelControllerBase.php#L537-L544

tschunk commented 1 month ago

Hi!

yes, I get the same error when using set with a unknown UUID.

script to reproduce:

#!/bin/bash
firewall="https://CHANGEME"
key=CHANGEME
secret=CHANGEME

set -ex
uuid="$(python3 -c 'import uuid; print(uuid.uuid4());')"

# try to upsert a rule
# will print `Fatal error: Allowed memory size of 1073741824 bytes exhausted`
curl -ksv "${firewall}/api/firewall/filter/set_rule/${uuid}" \
  --user "${key}:${secret}" \
  -X POST -H 'Content-Type: application/json' \
  --data-raw '{"rule":{"enabled":"1","sequence":"1","action":"pass","quick":"1","interface":"lan","direction":"in","ipprotocol":"inet","protocol":"TCP","source_net":"1.1.1.1","source_port":"","source_not":"0","destination_net":"2.2.2.2","destination_not":"0","destination_port":"80","gateway":"","log":"0","categories":"","description":"update"}}'

Output:

++ python3 -c 'import uuid; print(uuid.uuid4());'
+ uuid=c0c74609-a349-4088-9f0c-fcf7145ffc2d
+ curl -ksv https://XXXXXXX/api/firewall/filter/set_rule/c0c74609-a349-4088-9f0c-fcf7145ffc2d --user XXXXXXX -X POST -H 'Content-Type: application/json' --data-raw '{"rule":{"enabled":"1","sequence":"1","action":"pass","quick":"1","interface":"lan","direction":"in","ipprotocol":"inet","protocol":"TCP","source_net":"1.1.1.1","source_port":"","source_not":"0","destination_net":"2.2.2.2","destination_not":"0","destination_port":"80","gateway":"","log":"0","categories":"","description":"update"}}'
[...]
> POST /api/firewall/filter/set_rule/c0c74609-a349-4088-9f0c-fcf7145ffc2d HTTP/2
> Host: XXXXXXX
> Authorization: Basic XXXXXXXXX
> User-Agent: curl/8.6.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 332
>
< HTTP/2 200
< content-type: text/html; charset=UTF-8
< content-length: 191
< date: Mon, 15 Jul 2024 10:16:00 GMT
< server: OPNsense
<

Fatal error: Allowed memory size of 1073741824 bytes exhausted (tried to allocate 2097160 bytes) in /usr/local/opnsense/mvc/app/models/OPNsense/Base/FieldTypes/BaseListField.php on line 156

PS: I tested this now with latest version 24.1.10_2