Skip to content

IPcheck: unit + integration tests; fix exempt-address crash, rehash, and 16-bit/unsigned arithmetic bugs - #102

Open
MrIron-no wants to merge 2 commits into
UndernetIRC:mainfrom
MrIron-no:fix/ipcheck-tests
Open

IPcheck: unit + integration tests; fix exempt-address crash, rehash, and 16-bit/unsigned arithmetic bugs#102
MrIron-no wants to merge 2 commits into
UndernetIRC:mainfrom
MrIron-no:fix/ipcheck-tests

Conversation

@MrIron-no

Copy link
Copy Markdown
Contributor

Summary

Adds comprehensive tests for ircd/IPcheck.c (there were none) and fixes the five bugs they surfaced. Two commits: fixes (74307e5) and tests (2d57779).

Bugs fixed (74307e5)

  1. Server crash on disconnect of an exempt-address client. IPCheck { except ... } addresses are accepted without being recorded, but the client was still marked IPChecked, so IPcheck_connect_succeeded() reported a stale entry and IPcheck_disconnect() decremented a count it never incremented. With an entry present for the address (clients connected before the exemption, or remote users) the third exempt disconnect hit Assertion failure at IPcheck.c:665: "entry->connected > 0" and aborted the server (reproduced in the docker harness: ircu-limits exited 134); with asserts off the counter wraps and Client-block maxlinks stops working. Remote exempt clients were flagged the same way.
    IPcheck_local_connect() now returns IPCHECK_REFUSED / IPCHECK_COUNTED / IPCHECK_EXEMPT; callers (s_bsd.c, websocket.c, s_auth.c) mark the client IPChecked only when counted; ip_registry_check_remote() sets the flag only after the invalid/exempt early-outs; connect_succeeded / connect_fail call sites are guarded by IsIPChecked() as IPcheck_disconnect() already was.
  2. Exemptions survived a rehash that removed the IPCheck block (IPcheck_clear_config() was only called while parsing one). Now called from read_configuration_file().
  3. CONNECTED_SINCE() went negative for up to 65536 s after CurrentTime crossed a multiple of 65536 (every 18.2 h): clone period never reset, entries never expired, free-target regeneration underflowed. Reduced modulo 2^16.
  4. Zero free targets gave a full set: CurrentTime - (TARGET_DELAY * free_targets - 1) was evaluated in unsigned int, yielding CurrentTime - 4294967295 on 64-bit time_t instead of CurrentTime + 1.
  5. Remote-connect overflow guard refused the client but left connected wrapped to 0.

Behaviour changes worth noting: exempt clients no longer receive the (stale) on N ca M(L) ... notice at all; clients reconnecting from an address that exhausted its targets now get none, as the comments in ip_registry_disconnect() intend.

Tests (2d57779)

  • ircd/test/ipcheck_t.c (make check): fake clock + feature values; clone limit/period (incl. refused attempts restarting the period), address independence, boot grace, connect_fail undo, disconnect accounting and reset-on-last-disconnect, exemptions (result code, no flag, existing entry untouched, config clear), remote vs. burst, IPv6 /64 keying and /48 limit, IPv4 canonical form, free-target inheritance/pinning/regeneration/bonus, expiry pass, clock wrap, counter overflow.
  • tests/ipcheck/test_ipcheck_limits.py (limits server, SET-driven): registry notice contents, throttle ERROR before any input, recovery after the period, refused-attempt period restart, slot release, boot grace, IPCheck except block via rehash and its removal, Client-block maxlinks enforced from the registry.
  • tests/ipcheck/test_ipcheck_remote.py (hub): a P10-introduced user with the test host's address counts toward and is released from that address; another address does not. P10Server.introduce_user() gains ip=.

Verification

  • make -C ircd/test check: 7/7
  • tests/ipcheck/: 10/10 (docker)
  • Regression over class_limits, pr_websocket, cidr_glines, pr69_exempt_throttle: running at PR time; result will be posted as a comment.

…ic bugs

Found by the new IPcheck unit and integration tests.

- Exempt addresses (IPCheck { except ... }) were accepted without being
  recorded, but the client was still marked IPChecked, so
  IPcheck_connect_succeeded() reported a stale entry and
  IPcheck_disconnect() decremented a count it never incremented.  With an
  entry present for the address (clients connected before the exemption,
  or remote users), the third exempt disconnect tripped
  "Assertion failure at IPcheck.c:665: entry->connected > 0" and aborted
  the server; with asserts off the count wrapped and Client-block maxlinks
  stopped working.  Remote exempt clients were flagged the same way.
  IPcheck_local_connect() now returns IPCHECK_REFUSED / IPCHECK_COUNTED /
  IPCHECK_EXEMPT and callers mark the client IPChecked only when it was
  counted; ip_registry_check_remote() sets the flag only after the invalid
  and exempt early-outs; the post-hooks (connect_succeeded, connect_fail)
  are guarded by IsIPChecked() like IPcheck_disconnect() already was.

- Removing the IPCheck block from the config and rehashing left the old
  exemptions in force: IPcheck_clear_config() was only called while
  parsing an IPCheck block.  Call it from read_configuration_file().

- CONNECTED_SINCE() subtracted two 16-bit timestamps in int, so it went
  negative for up to 65536 s after CurrentTime crossed a multiple of 65536
  (every 18.2 hours): the clone period never reset, entries did not expire
  and free-target regeneration underflowed.  Reduce modulo 2^16.

- With no free targets left, `CurrentTime - (TARGET_DELAY * free_targets
  - 1)` was computed in unsigned int, giving CurrentTime - 4294967295 on
  64-bit time_t instead of CurrentTime + 1: a client returning from an
  address that had exhausted its targets got a full set instead of none.

- The remote-connect overflow guard refused the client but left the
  connected counter wrapped to zero.
ircd/test/ipcheck_t.c (make check) drives ircd/IPcheck.c through its public
API with a fake clock and feature values: clone limit and period (including
refused attempts restarting the period), address independence, boot grace
(IPCHECK_CLONE_DELAY), connect_fail() undo, disconnect accounting and the
reset on last disconnect, exemptions (result code, no flag, no interference
with an existing entry, cleared config), remote vs. burst introductions,
IPv6 /64 keying and the /48 limit, IPv4 canonical form, free-target
inheritance / pinning / regeneration / long-connection bonus, the expiry
pass, the 16-bit clock wrap and the connected-counter overflow guard.

tests/ipcheck/ (docker):
- test_ipcheck_limits.py on the limits server, driving the limits with SET
  (PRIV_SET granted by rehash): registry notice contents, throttle ERROR
  before any input, recovery after the period, refused attempts restarting
  the period, slot release on disconnect, boot grace, IPCheck except block
  via rehash (and its removal), Client-block maxlinks enforced from the
  registry.
- test_ipcheck_remote.py on the hub: a P10-introduced user with the test
  host's address counts toward (and is released from) the address, one
  with another address does not.  P10Server.introduce_user() gains an ip
  parameter for this.
@MrIron-no

Copy link
Copy Markdown
Contributor Author

Regression run over the suites that share the touched paths (class_limits, pr_websocket, cidr_glines, pr69_exempt_throttle): 107 passed, 1 failed.

The failure is cidr_glines/test_family_ambiguous_masks.py::test_ambiguous_mask_blocks_reconnect (victim client gets neither ERROR nor 001 within 5 s after a *victim@::/16 G-line). It fails identically on current main (0200c5d) with this branch's changes absent, so it is pre-existing and not related to IPcheck; noting it here rather than folding an unrelated fix into this PR.

Also confirmed after the fixes: make -C ircd/test check 7/7, tests/ipcheck/ 10/10.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant