From 5ef38df66af87d150672acb5247139d586a56ca6 Mon Sep 17 00:00:00 2001 From: Domingo Dirutigliano Date: Tue, 18 Feb 2025 21:20:19 +0100 Subject: [PATCH] regex checked by hyperscan directly with error messages --- backend/binsrc/nfregex.cpp | 15 +++++++++++++++ backend/binsrc/regex/regex_rules.cpp | 22 ++++++++++++++++++++++ backend/modules/nfregex/firegex.py | 16 ++++++++++++++-- backend/routers/nfregex.py | 9 ++++----- tests/benchmark.py | 16 +++++++++++++++- 5 files changed, 70 insertions(+), 8 deletions(-) diff --git a/backend/binsrc/nfregex.cpp b/backend/binsrc/nfregex.cpp index dacd6c1..a37e2b2 100644 --- a/backend/binsrc/nfregex.cpp +++ b/backend/binsrc/nfregex.cpp @@ -50,6 +50,21 @@ void config_updater (){ } int main(int argc, char *argv[]){ + + char * test_regex = getenv("FIREGEX_TEST_REGEX"); + if (test_regex != nullptr){ + cerr << "[info] [main] Testing regex: " << test_regex << endl; + try{ + RegexRules::compile_regex(test_regex); + cerr << "[info] [main] Test passed" << endl; + return 0; + }catch(const std::exception& e){ + cerr << "[error] [updater] Test failed" << endl; + cout << e.what() << flush; + return 1; + } + } + int n_of_threads = 1; char * n_threads_str = getenv("NTHREADS"); if (n_threads_str != nullptr) n_of_threads = ::atoi(n_threads_str); diff --git a/backend/binsrc/regex/regex_rules.cpp b/backend/binsrc/regex/regex_rules.cpp index c59ad45..71ef786 100644 --- a/backend/binsrc/regex/regex_rules.cpp +++ b/backend/binsrc/regex/regex_rules.cpp @@ -59,6 +59,26 @@ class RegexRules{ public: regex_ruleset output_ruleset, input_ruleset; + static void compile_regex(char* regex){ + hs_database_t* db = nullptr; + hs_compile_error_t *compile_err = nullptr; + if ( + hs_compile( + regex, + HS_FLAG_SINGLEMATCH | HS_FLAG_ALLOWEMPTY, + HS_MODE_BLOCK, + nullptr, &db, &compile_err + ) != HS_SUCCESS + ) { + string err = string(compile_err->message); + hs_free_compile_error(compile_err); + throw runtime_error(err); + }else{ + hs_free_database(db); + } + + } + private: static inline u_int16_t glob_seq = 0; u_int16_t version; @@ -77,6 +97,8 @@ class RegexRules{ } } + + void fill_ruleset(vector> & decoded, regex_ruleset & ruleset){ size_t n_of_regex = decoded.size(); if (n_of_regex == 0){ diff --git a/backend/modules/nfregex/firegex.py b/backend/modules/nfregex/firegex.py index 026b832..3d14bda 100644 --- a/backend/modules/nfregex/firegex.py +++ b/backend/modules/nfregex/firegex.py @@ -1,7 +1,6 @@ from modules.nfregex.nftables import FiregexTables from utils import run_func from modules.nfregex.models import Service, Regex -import re import os import asyncio import traceback @@ -10,6 +9,20 @@ from fastapi import HTTPException nft = FiregexTables() +async def test_regex_validity(regex: str) -> bool: + proxy_binary_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),"../cppregex") + process = await asyncio.create_subprocess_exec( + proxy_binary_path, + stdout=asyncio.subprocess.PIPE, + stdin=asyncio.subprocess.DEVNULL, + env={"FIREGEX_TEST_REGEX": regex}, + ) + await process.wait() + if process.returncode != 0: + message = (await process.stdout.read()).decode() + return False, message + return True, "ok" + class RegexFilter: def __init__( self, regex, @@ -44,7 +57,6 @@ class RegexFilter: self.regex = self.regex.encode() if not isinstance(self.regex, bytes): raise Exception("Invalid Regex Paramether") - re.compile(self.regex) # raise re.error if it's invalid! case_sensitive = "1" if self.is_case_sensitive else "0" if self.input_mode: yield case_sensitive + "C" + self.regex.hex() diff --git a/backend/routers/nfregex.py b/backend/routers/nfregex.py index 744b6f2..ae09d22 100644 --- a/backend/routers/nfregex.py +++ b/backend/routers/nfregex.py @@ -1,5 +1,4 @@ from base64 import b64decode -import re import secrets import sqlite3 from fastapi import APIRouter, Response, HTTPException @@ -9,6 +8,7 @@ from modules.nfregex.firewall import STATUS, FirewallManager from utils.sqlite import SQLite from utils import ip_parse, refactor_name, socketio_emit, PortType from utils.models import ResetRequest, StatusMessageModel +from modules.nfregex.firegex import test_regex_validity class ServiceModel(BaseModel): status: str @@ -299,10 +299,9 @@ async def regex_disable(regex_id: int): @app.post('/regexes', response_model=StatusMessageModel) async def add_new_regex(form: RegexAddForm): """Add a new regex""" - try: - re.compile(b64decode(form.regex)) - except Exception: - raise HTTPException(status_code=400, detail="Invalid regex") + regex_correct, message = await test_regex_validity(b64decode(form.regex)) + if not regex_correct: + raise HTTPException(status_code=400, detail=f"Invalid regex: {message}") try: db.query("INSERT INTO regexes (service_id, regex, mode, is_case_sensitive, active ) VALUES (?, ?, ?, ?, ?);", form.service_id, form.regex, form.mode, form.is_case_sensitive, True if form.active is None else form.active ) diff --git a/tests/benchmark.py b/tests/benchmark.py index 06044c1..b69e605 100644 --- a/tests/benchmark.py +++ b/tests/benchmark.py @@ -45,6 +45,11 @@ def exit_test(code): #Create new Service +srvs = firegex.nf_get_services() +for ele in srvs: + if ele['name'] == args.service_name: + firegex.nf_delete_service(ele['service_id']) + service_id = firegex.nf_add_service(args.service_name, args.port, "tcp", "127.0.0.1/24") if service_id: puts(f"Sucessfully created service {service_id} ✔", color=colors.green) @@ -52,6 +57,10 @@ else: puts("Test Failed: Failed to create service ✗", color=colors.red) exit(1) +args.port = int(args.port) +args.duration = int(args.duration) +args.num_of_streams = int(args.num_of_streams) + #Start iperf3 def startServer(): server = iperf3.Server() @@ -66,6 +75,8 @@ def getReading(port): client.duration = args.duration client.server_hostname = '127.0.0.1' client.port = port + client.zerocopy = True + client.verbose = False client.protocol = 'tcp' client.num_streams = args.num_of_streams return round(client.run().json['end']['sum_received']['bits_per_second']/8e+6 , 3) @@ -74,6 +85,9 @@ server = Process(target=startServer) server.start() sleep(1) +def gen_regex(): + regex = secrets.token_hex(8) + return base64.b64encode(bytes(regex.encode())).decode() #Get baseline reading puts("Baseline without proxy: ", color=colors.blue, end='') @@ -95,7 +109,7 @@ print(f"{results[0]} MB/s") #Add all the regexs for i in range(1,args.num_of_regexes+1): - regex = base64.b64encode(bytes(secrets.token_hex(16).encode())).decode() + regex = gen_regex() if not firegex.nf_add_regex(service_id,regex,"B",active=True,is_case_sensitive=False): puts("Benchmark Failed: Couldn't add the regex ✗", color=colors.red) exit_test(1)