1 # Copyright (C) Internet Systems Consortium, Inc. ("ISC") 2 # 3 # SPDX-License-Identifier: MPL-2.0 4 # 5 # This Source Code Form is subject to the terms of the Mozilla Public 6 # License, v. 2.0. If a copy of the MPL was not distributed with this 7 # file, you can obtain one at https://mozilla.org/MPL/2.0/. 8 # 9 # See the COPYRIGHT file distributed with this work for additional 10 # information regarding copyright ownership. 11 12 """ 13 Regression test for GL#5006: TOCTOU race in DNS UPDATE SSU table handling. 14 15 send_update() and update_action() used to independently read the zone's 16 SSU table. If rndc reconfig changed the zone's update policy between 17 these two reads, the values could diverge, causing an assertion failure. 18 19 This test races rndc reconfig (toggling between allow-update and 20 update-policy) against a stream of DNS UPDATEs to verify that named 21 survives without crashing. 22 """ 23 24 import threading 25 import time 26 27 import dns.query 28 import dns.rdatatype 29 import dns.update 30 import pytest 31 32 import isctest 33 34 pytestmark = pytest.mark.extra_artifacts( 35 [ 36 "*/*.db", 37 "*/*.jnl", 38 ] 39 ) 40 41 42 def send_updates(ip, port, stop_event): 43 """Send DNS UPDATEs in a tight loop until stopped.""" 44 n = 0 45 while not stop_event.is_set(): 46 n += 1 47 try: 48 up = dns.update.UpdateMessage("example.") 49 up.add( 50 f"test{n}.example.", 51 300, 52 dns.rdatatype.A, 53 f"10.0.0.{n % 256}", 54 ) 55 dns.query.tcp(up, ip, port=port, timeout=2) 56 except Exception: # pylint: disable=broad-exception-caught 57 pass 58 59 60 def toggle_config(ns1, templates, stop_event): 61 """Toggle zone config between allow-update and update-policy.""" 62 use_ssu = False 63 while not stop_event.is_set(): 64 use_ssu = not use_ssu 65 try: 66 templates.render("ns1/named.conf", {"use_ssu": use_ssu}) 67 ns1.rndc("reconfig") 68 except Exception: # pylint: disable=broad-exception-caught 69 pass 70 time.sleep(0.01) 71 72 73 def test_ssu_toctou_race(ns1, templates): 74 """Race rndc reconfig against DNS UPDATEs -- named must not crash.""" 75 port = int(isctest.vars.ALL["PORT"]) 76 stop = threading.Event() 77 78 update_thread = threading.Thread( 79 target=send_updates, 80 args=("10.53.0.1", port, stop), 81 ) 82 reconfig_thread = threading.Thread( 83 target=toggle_config, 84 args=(ns1, templates, stop), 85 ) 86 87 update_thread.start() 88 reconfig_thread.start() 89 90 # Let them race for a few seconds 91 time.sleep(5) 92 93 stop.set() 94 update_thread.join(timeout=10) 95 reconfig_thread.join(timeout=10) 96 97 # Restore original config 98 templates.render("ns1/named.conf", {"use_ssu": False}) 99 ns1.rndc("reconfig") 100 101 # Verify named is still alive 102 msg = isctest.query.create("ns.example.", "A") 103 res = isctest.query.udp(msg, "10.53.0.1") 104 isctest.check.noerror(res) 105