@@ -43,7 +43,7 @@ pub mod memory_profile;
4343use std:: ffi:: { CStr , CString } ;
4444use std:: ptr:: { self , null_mut} ;
4545use std:: slice;
46- use std:: sync:: atomic:: { AtomicBool , AtomicI64 , AtomicU64 , Ordering } ;
46+ use std:: sync:: atomic:: { AtomicBool , AtomicI32 , AtomicI64 , AtomicU64 , Ordering } ;
4747use std:: sync:: Arc ;
4848use std:: thread:: { self , JoinHandle } ;
4949use std:: time:: Duration ;
@@ -679,6 +679,65 @@ static mut ON_CPU_PROFILE_FREQUENCY: u32 = 0;
679679static mut PROFILE_STACK_COMPRESSION : bool = true ;
680680#[ allow( static_mut_refs) ]
681681static mut TIME_DIFF : Option < Arc < AtomicI64 > > = None ;
682+ #[ cfg( feature = "enterprise" ) ]
683+ static AI_AGENT_EXEC_RULES_MAP_FD : AtomicI32 = AtomicI32 :: new ( -1 ) ;
684+ #[ cfg( feature = "enterprise" ) ]
685+ static AI_AGENT_POLICY_EPOCH_MAP_FD : AtomicI32 = AtomicI32 :: new ( -1 ) ;
686+ #[ cfg( feature = "enterprise" ) ]
687+ const AI_AGENT_EXEC_RULES_BPF_MAX : usize = 256 ;
688+
689+ #[ cfg( feature = "enterprise" ) ]
690+ fn ai_agent_enforcement_mode_eq ( value : & str , expected : & str ) -> bool {
691+ value. trim ( ) . eq_ignore_ascii_case ( expected)
692+ }
693+
694+ #[ cfg( feature = "enterprise" ) ]
695+ fn ai_agent_enforcement_lsm_allowed (
696+ config : & crate :: config:: config:: AiAgentEnforcementConfig ,
697+ ) -> bool {
698+ let mechanism_allowed = config
699+ . allowed_mechanisms
700+ . iter ( )
701+ . any ( |m| ai_agent_enforcement_mode_eq ( m, "lsm" ) ) ;
702+ let strategy_allows_lsm = matches ! (
703+ config. strategy. trim( ) . to_ascii_lowercase( ) . as_str( ) ,
704+ "auto" | "lsm_only"
705+ ) ;
706+ mechanism_allowed && strategy_allows_lsm
707+ }
708+
709+ #[ cfg( feature = "enterprise" ) ]
710+ fn ai_agent_enforcement_inputs (
711+ config : & crate :: config:: config:: AiAgentEnforcementConfig ,
712+ mode : enterprise_utils:: ai_agent_enforcement:: EnforcementMode ,
713+ ) -> Vec < enterprise_utils:: ai_agent_enforcement:: ExecRuleInput > {
714+ config
715+ . rules
716+ . iter ( )
717+ . filter ( |rule| {
718+ ai_agent_enforcement_mode_eq ( & rule. scope , "ai_agent_tree" )
719+ && ai_agent_enforcement_mode_eq ( & rule. target_type , "exec" )
720+ } )
721+ . map ( |rule| {
722+ let rule_mode = if mode
723+ == enterprise_utils:: ai_agent_enforcement:: EnforcementMode :: Block
724+ && ai_agent_enforcement_mode_eq ( & rule. action . action_type , "deny" )
725+ {
726+ enterprise_utils:: ai_agent_enforcement:: EnforcementMode :: Block
727+ } else {
728+ enterprise_utils:: ai_agent_enforcement:: EnforcementMode :: AuditOnly
729+ } ;
730+ enterprise_utils:: ai_agent_enforcement:: ExecRuleInput {
731+ id : rule. id . clone ( ) ,
732+ mode : rule_mode,
733+ exact : rule. exec . exact . clone ( ) ,
734+ prefix : rule. exec . prefix . clone ( ) ,
735+ suffix : rule. exec . suffix . clone ( ) ,
736+ argv_contains_any : rule. exec . argv_contains_any . clone ( ) ,
737+ }
738+ } )
739+ . collect ( )
740+ }
682741
683742pub unsafe fn string_from_null_terminated_c_str ( ptr : * const u8 ) -> String {
684743 CStr :: from_ptr ( ptr as * const libc:: c_char )
@@ -1444,6 +1503,28 @@ impl EbpfCollector {
14441503 } else {
14451504 warn ! ( "AI Agent: could not find __ai_agent_pids BPF map (fd={}), file I/O monitoring will not work" , fd) ;
14461505 }
1506+
1507+ let exec_rules_fd = unsafe {
1508+ ebpf:: bpf_table_get_map_fd (
1509+ c"socket-trace" . as_ptr ( ) ,
1510+ c"__ai_agent_exec_rules" . as_ptr ( ) ,
1511+ )
1512+ } ;
1513+ AI_AGENT_EXEC_RULES_MAP_FD . store ( exec_rules_fd, Ordering :: Relaxed ) ;
1514+ let policy_epoch_fd = unsafe {
1515+ ebpf:: bpf_table_get_map_fd (
1516+ c"socket-trace" . as_ptr ( ) ,
1517+ c"__ai_agent_policy_epoch" . as_ptr ( ) ,
1518+ )
1519+ } ;
1520+ AI_AGENT_POLICY_EPOCH_MAP_FD . store ( policy_epoch_fd, Ordering :: Relaxed ) ;
1521+ if exec_rules_fd < 0 || policy_epoch_fd < 0 {
1522+ warn ! (
1523+ "AI Agent enforcement: BPF maps unavailable (__ai_agent_exec_rules={}, __ai_agent_policy_epoch={}), block mode will downgrade to audit-only" ,
1524+ exec_rules_fd, policy_epoch_fd
1525+ ) ;
1526+ }
1527+ Self :: sync_ai_agent_enforcement_policy ( & config. ai_agent_enforcement ) ;
14471528 }
14481529
14491530 Ok ( handle)
@@ -1484,6 +1565,92 @@ impl EbpfCollector {
14841565 }
14851566 }
14861567
1568+ #[ cfg( feature = "enterprise" ) ]
1569+ fn clear_ai_agent_enforcement_bpf_maps ( max_records : usize ) {
1570+ let exec_rules_fd = AI_AGENT_EXEC_RULES_MAP_FD . load ( Ordering :: Relaxed ) ;
1571+ let policy_epoch_fd = AI_AGENT_POLICY_EPOCH_MAP_FD . load ( Ordering :: Relaxed ) ;
1572+ if exec_rules_fd < 0 || policy_epoch_fd < 0 {
1573+ return ;
1574+ }
1575+ match enterprise_utils:: ai_agent_enforcement:: compile_exec_rules ( & [ ] ) {
1576+ Ok ( policy) => {
1577+ if let Err ( e) = policy. sync_to_bpf_maps ( exec_rules_fd, policy_epoch_fd, max_records)
1578+ {
1579+ warn ! ( "AI Agent enforcement: failed to clear BPF maps: {}" , e) ;
1580+ }
1581+ }
1582+ Err ( e) => warn ! ( "AI Agent enforcement: failed to build empty policy: {}" , e) ,
1583+ }
1584+ }
1585+
1586+ #[ cfg( feature = "enterprise" ) ]
1587+ fn sync_ai_agent_enforcement_policy ( config : & crate :: config:: config:: AiAgentEnforcementConfig ) {
1588+ use enterprise_utils:: ai_agent_enforcement:: {
1589+ compile_exec_rules, set_global_exec_policy, EnforcementMode ,
1590+ } ;
1591+
1592+ let max_records = config. max_rules . min ( AI_AGENT_EXEC_RULES_BPF_MAX ) ;
1593+ if !config. enabled {
1594+ set_global_exec_policy ( None ) ;
1595+ Self :: clear_ai_agent_enforcement_bpf_maps ( max_records) ;
1596+ return ;
1597+ }
1598+
1599+ let exec_rules_fd = AI_AGENT_EXEC_RULES_MAP_FD . load ( Ordering :: Relaxed ) ;
1600+ let policy_epoch_fd = AI_AGENT_POLICY_EPOCH_MAP_FD . load ( Ordering :: Relaxed ) ;
1601+ let bpf_maps_available = exec_rules_fd >= 0 && policy_epoch_fd >= 0 ;
1602+ let lsm_allowed = ai_agent_enforcement_lsm_allowed ( config) ;
1603+ let requested_block = ai_agent_enforcement_mode_eq ( & config. mode , "block" ) ;
1604+ let effective_mode = if requested_block && bpf_maps_available && lsm_allowed {
1605+ EnforcementMode :: Block
1606+ } else {
1607+ if requested_block {
1608+ warn ! (
1609+ "AI Agent enforcement: block mode requested but BPF LSM is unavailable or disallowed; downgrade to audit-only (maps_available={}, lsm_allowed={})" ,
1610+ bpf_maps_available, lsm_allowed
1611+ ) ;
1612+ }
1613+ EnforcementMode :: AuditOnly
1614+ } ;
1615+
1616+ let inputs = ai_agent_enforcement_inputs ( config, effective_mode) ;
1617+ let policy = match compile_exec_rules ( & inputs) {
1618+ Ok ( policy) => policy,
1619+ Err ( e) => {
1620+ warn ! ( "AI Agent enforcement: failed to compile policy: {}" , e) ;
1621+ set_global_exec_policy ( None ) ;
1622+ Self :: clear_ai_agent_enforcement_bpf_maps ( max_records) ;
1623+ return ;
1624+ }
1625+ } ;
1626+
1627+ if effective_mode == EnforcementMode :: Block {
1628+ if let Err ( e) = policy. sync_to_bpf_maps ( exec_rules_fd, policy_epoch_fd, max_records) {
1629+ warn ! (
1630+ "AI Agent enforcement: failed to sync BPF policy, downgrade to audit-only: {}" ,
1631+ e
1632+ ) ;
1633+ let audit_inputs = ai_agent_enforcement_inputs ( config, EnforcementMode :: AuditOnly ) ;
1634+ match compile_exec_rules ( & audit_inputs) {
1635+ Ok ( audit_policy) => set_global_exec_policy ( Some ( audit_policy) ) ,
1636+ Err ( e) => {
1637+ warn ! (
1638+ "AI Agent enforcement: failed to compile audit policy: {}" ,
1639+ e
1640+ ) ;
1641+ set_global_exec_policy ( None ) ;
1642+ }
1643+ }
1644+ Self :: clear_ai_agent_enforcement_bpf_maps ( max_records) ;
1645+ return ;
1646+ }
1647+ } else {
1648+ Self :: clear_ai_agent_enforcement_bpf_maps ( max_records) ;
1649+ }
1650+
1651+ set_global_exec_policy ( Some ( policy) ) ;
1652+ }
1653+
14871654 fn ebpf_start ( ) {
14881655 debug ! ( "ebpf collector starting ebpf-kernel." ) ;
14891656 unsafe {
@@ -1693,6 +1860,8 @@ impl EbpfCollector {
16931860 config. l7_log_packet_size ,
16941861 config. ai_agent_max_payload_size ,
16951862 ) ;
1863+ #[ cfg( feature = "enterprise" ) ]
1864+ Self :: sync_ai_agent_enforcement_policy ( & config. ai_agent_enforcement ) ;
16961865
16971866 #[ cfg( feature = "extended_observability" ) ]
16981867 {
0 commit comments