@@ -4,7 +4,7 @@ use crossbeam::channel::{self, Receiver, Sender};
44use dashmap:: DashMap ;
55use log:: { debug, error, info, warn} ;
66use serde_json:: json;
7- use std:: fs:: OpenOptions ;
7+ use std:: fs:: { File , OpenOptions } ;
88use std:: io:: Write ;
99use std:: sync:: atomic:: { AtomicBool , AtomicU64 , Ordering } ;
1010use std:: sync:: { Arc , RwLock } ;
@@ -101,6 +101,30 @@ impl ProcessDetectionStatus {
101101static QUIC_CONNECTION_MAPPING : LazyLock < Mutex < HashMap < String , String > > > =
102102 LazyLock :: new ( || Mutex :: new ( HashMap :: new ( ) ) ) ;
103103
104+ /// Maximum QUIC connection ID mappings to prevent unbounded growth.
105+ const MAX_QUIC_MAPPINGS : usize = 10_000 ;
106+
107+ /// Maximum queued packets before backpressure drops packets.
108+ /// At ~1500 bytes per packet, 10,000 packets ≈ 15 MB of buffer.
109+ const MAX_PACKET_QUEUE : usize = 10_000 ;
110+
111+ /// Maximum tracked connections to prevent memory exhaustion from port scans
112+ /// or connection floods.
113+ const MAX_CONNECTIONS : usize = 50_000 ;
114+
115+ /// Open or create a file for appending with restrictive permissions (0o600 on Unix).
116+ ///
117+ /// Ensures log files containing connection metadata are not world-readable.
118+ fn open_log_file ( path : & str ) -> std:: io:: Result < File > {
119+ let file = OpenOptions :: new ( ) . create ( true ) . append ( true ) . open ( path) ?;
120+ #[ cfg( unix) ]
121+ {
122+ use std:: os:: unix:: fs:: PermissionsExt ;
123+ let _ = file. set_permissions ( std:: fs:: Permissions :: from_mode ( 0o600 ) ) ;
124+ }
125+ Ok ( file)
126+ }
127+
104128/// Helper function to log connection events as JSON
105129fn log_connection_event (
106130 json_log_path : & str ,
@@ -213,11 +237,8 @@ fn log_connection_event(
213237 }
214238 }
215239
216- // Write to file
217- if let Ok ( mut file) = OpenOptions :: new ( )
218- . create ( true )
219- . append ( true )
220- . open ( json_log_path)
240+ // Write to file (restrictive permissions: 0o600 on Unix)
241+ if let Ok ( mut file) = open_log_file ( json_log_path)
221242 && let Ok ( json_str) = serde_json:: to_string ( & event)
222243 {
223244 let _ = writeln ! ( file, "{}" , json_str) ;
@@ -265,10 +286,7 @@ fn log_pcap_connection(pcap_path: &str, conn: &Connection) {
265286 }
266287 }
267288
268- if let Ok ( mut file) = OpenOptions :: new ( )
269- . create ( true )
270- . append ( true )
271- . open ( & json_path)
289+ if let Ok ( mut file) = open_log_file ( & json_path)
272290 && let Ok ( json_str) = serde_json:: to_string ( & event)
273291 {
274292 let _ = writeln ! ( file, "{}" , json_str) ;
@@ -549,7 +567,7 @@ impl App {
549567 connections : Arc < DashMap < String , Connection > > ,
550568 ) -> Result < ( ) > {
551569 // Create packet channel
552- let ( packet_tx, packet_rx) = channel:: unbounded ( ) ;
570+ let ( packet_tx, packet_rx) = channel:: bounded ( MAX_PACKET_QUEUE ) ;
553571
554572 // Start capture thread
555573 self . start_capture_thread ( packet_tx) ?;
@@ -592,6 +610,18 @@ impl App {
592610 * current_interface. write ( ) . unwrap ( ) = Some ( device_name. clone ( ) ) ;
593611 * linktype_storage. write ( ) . unwrap ( ) = Some ( linktype) ;
594612
613+ // Drop CAP_NET_RAW now that the socket is open (Linux only)
614+ #[ cfg( all( target_os = "linux" , feature = "landlock" ) ) ]
615+ {
616+ if let Err ( e) =
617+ crate :: network:: platform:: sandbox:: capabilities:: drop_cap_net_raw ( )
618+ {
619+ warn ! ( "Failed to drop CAP_NET_RAW in capture thread: {}" , e) ;
620+ } else {
621+ debug ! ( "Dropped CAP_NET_RAW in capture thread" ) ;
622+ }
623+ }
624+
595625 // Check if PKTAP is active (linktype 149 or 258)
596626 #[ cfg( target_os = "macos" ) ]
597627 {
@@ -678,9 +708,17 @@ impl App {
678708 } ) ;
679709 }
680710
681- if packet_tx. send ( packet) . is_err ( ) {
682- warn ! ( "Packet channel closed" ) ;
683- break ;
711+ match packet_tx. try_send ( packet) {
712+ Ok ( ( ) ) => { }
713+ Err ( crossbeam:: channel:: TrySendError :: Full ( _) ) => {
714+ stats
715+ . packets_dropped
716+ . fetch_add ( 1 , Ordering :: Relaxed ) ;
717+ }
718+ Err ( crossbeam:: channel:: TrySendError :: Disconnected ( _) ) => {
719+ warn ! ( "Packet channel closed" ) ;
720+ break ;
721+ }
684722 }
685723 }
686724 Ok ( None ) => {
@@ -767,6 +805,17 @@ impl App {
767805 thread:: spawn ( move || {
768806 info ! ( "Packet processor {} started" , id) ;
769807
808+ // Drop CAP_NET_RAW immediately as this thread doesn't need it (Linux only)
809+ #[ cfg( all( target_os = "linux" , feature = "landlock" ) ) ]
810+ {
811+ if let Err ( e) = crate :: network:: platform:: sandbox:: capabilities:: drop_cap_net_raw ( )
812+ {
813+ warn ! ( "Failed to drop CAP_NET_RAW in processor thread {}: {}" , id, e) ;
814+ } else {
815+ debug ! ( "Dropped CAP_NET_RAW in processor thread {}" , id) ;
816+ }
817+ }
818+
770819 // Wait for linktype to be available
771820 let parser = loop {
772821 if let Some ( linktype) = * linktype_storage. read ( ) . unwrap ( ) {
@@ -1403,7 +1452,7 @@ impl App {
14031452 ) ;
14041453 }
14051454
1406- thread:: sleep ( Duration :: from_secs ( 10 ) ) ;
1455+ thread:: sleep ( Duration :: from_secs ( 5 ) ) ;
14071456 }
14081457 } ) ;
14091458
@@ -1696,6 +1745,11 @@ fn update_connection(
16961745 key, conn_id_hex
16971746 ) ;
16981747 } else {
1748+ // Prevent unbounded growth of QUIC connection ID mappings
1749+ if mapping. len ( ) >= MAX_QUIC_MAPPINGS {
1750+ debug ! ( "QUIC mapping limit reached, clearing old entries" ) ;
1751+ mapping. clear ( ) ;
1752+ }
16991753 // New QUIC connection ID, create mapping
17001754 mapping. insert ( conn_id_hex. clone ( ) , key. clone ( ) ) ;
17011755 debug ! (
@@ -1705,6 +1759,16 @@ fn update_connection(
17051759 }
17061760 }
17071761
1762+ // Prevent unbounded growth from port scans or connection floods.
1763+ // Only limit new connections; existing ones always get updated.
1764+ if !connections. contains_key ( & key) && connections. len ( ) >= MAX_CONNECTIONS {
1765+ debug ! (
1766+ "Connection limit reached ({}), dropping new connection: {}" ,
1767+ MAX_CONNECTIONS , key
1768+ ) ;
1769+ return ;
1770+ }
1771+
17081772 connections
17091773 . entry ( key. clone ( ) )
17101774 . and_modify ( |conn| {
0 commit comments