Skip to content

Commit a69a1e5

Browse files
committed
fix: enforce audited network policy
Ensure network host calls check internet policy Log denied host function attempts and attach audit metadata
1 parent d04bf46 commit a69a1e5

2 files changed

Lines changed: 78 additions & 10 deletions

File tree

crates/wasm-runtime-interface/src/network.rs

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,11 @@ pub struct NetworkState {
147147
}
148148

149149
impl NetworkState {
150+
/// Create network state for a single WASM execution.
151+
///
152+
/// The policy is validated and then enforced by every host call. All host
153+
/// functions share the same counters and audit logger, providing a single
154+
/// enforcement surface for HTTP and DNS access.
150155
pub fn new(
151156
policy: NetworkPolicy,
152157
audit_logger: Option<Arc<dyn NetworkAuditLogger>>,
@@ -218,6 +223,11 @@ impl NetworkState {
218223
&mut self,
219224
request: HttpRequest,
220225
) -> Result<HttpResponse, NetworkError> {
226+
if !self.policy.allow_internet {
227+
self.audit_denial("http_request denied: network disabled");
228+
return Err(NetworkError::NetworkDisabled);
229+
}
230+
221231
self.ensure_request_budget()?;
222232

223233
if let Err(e) = self.validate_http_request(&request) {
@@ -277,6 +287,11 @@ impl NetworkState {
277287
}
278288

279289
pub fn handle_dns_request(&mut self, request: DnsRequest) -> Result<DnsResponse, NetworkError> {
290+
if !self.policy.allow_internet {
291+
self.audit_denial("dns_lookup denied: network disabled");
292+
return Err(NetworkError::NetworkDisabled);
293+
}
294+
280295
self.ensure_dns_budget()?;
281296

282297
if let Err(e) = self
@@ -522,33 +537,39 @@ fn handle_http_request(
522537
resp_ptr: i32,
523538
resp_len: i32,
524539
) -> i32 {
540+
let enforcement = "http_request";
525541
let request_bytes = match read_memory(caller, req_ptr, req_len) {
526542
Ok(bytes) => bytes,
527543
Err(err) => {
544+
warn!(challenge_id = %caller.data().challenge_id, validator_id = %caller.data().validator_id, function = enforcement, error = %err, "host memory read failed");
528545
return write_result::<HttpResponse>(
529546
caller,
530547
resp_ptr,
531548
resp_len,
532549
Err(NetworkError::HttpFailure(err)),
533-
)
550+
);
534551
}
535552
};
536553

537554
let request = match bincode::deserialize::<HttpRequest>(&request_bytes) {
538555
Ok(req) => req,
539556
Err(err) => {
557+
warn!(challenge_id = %caller.data().challenge_id, validator_id = %caller.data().validator_id, function = enforcement, error = %err, "host request decode failed");
540558
return write_result::<HttpResponse>(
541559
caller,
542560
resp_ptr,
543561
resp_len,
544562
Err(NetworkError::HttpFailure(format!(
545563
"invalid http request payload: {err}"
546564
))),
547-
)
565+
);
548566
}
549567
};
550568

551569
let result = caller.data_mut().network_state.handle_http_request(request);
570+
if let Err(ref err) = result {
571+
warn!(challenge_id = %caller.data().challenge_id, validator_id = %caller.data().validator_id, function = enforcement, error = %err, "host request denied");
572+
}
552573
write_result(caller, resp_ptr, resp_len, result)
553574
}
554575

@@ -559,29 +580,32 @@ fn handle_http_get(
559580
resp_ptr: i32,
560581
resp_len: i32,
561582
) -> i32 {
583+
let enforcement = "http_get";
562584
let request_bytes = match read_memory(caller, req_ptr, req_len) {
563585
Ok(bytes) => bytes,
564586
Err(err) => {
587+
warn!(challenge_id = %caller.data().challenge_id, validator_id = %caller.data().validator_id, function = enforcement, error = %err, "host memory read failed");
565588
return write_result::<HttpResponse>(
566589
caller,
567590
resp_ptr,
568591
resp_len,
569592
Err(NetworkError::HttpFailure(err)),
570-
)
593+
);
571594
}
572595
};
573596

574597
let request = match bincode::deserialize::<HttpGetRequest>(&request_bytes) {
575598
Ok(req) => req,
576599
Err(err) => {
600+
warn!(challenge_id = %caller.data().challenge_id, validator_id = %caller.data().validator_id, function = enforcement, error = %err, "host request decode failed");
577601
return write_result::<HttpResponse>(
578602
caller,
579603
resp_ptr,
580604
resp_len,
581605
Err(NetworkError::HttpFailure(format!(
582606
"invalid http get payload: {err}"
583607
))),
584-
)
608+
);
585609
}
586610
};
587611

@@ -593,6 +617,9 @@ fn handle_http_get(
593617
};
594618

595619
let result = caller.data_mut().network_state.handle_http_request(request);
620+
if let Err(ref err) = result {
621+
warn!(challenge_id = %caller.data().challenge_id, validator_id = %caller.data().validator_id, function = enforcement, error = %err, "host request denied");
622+
}
596623
write_result(caller, resp_ptr, resp_len, result)
597624
}
598625

@@ -603,29 +630,32 @@ fn handle_http_post(
603630
resp_ptr: i32,
604631
resp_len: i32,
605632
) -> i32 {
633+
let enforcement = "http_post";
606634
let request_bytes = match read_memory(caller, req_ptr, req_len) {
607635
Ok(bytes) => bytes,
608636
Err(err) => {
637+
warn!(challenge_id = %caller.data().challenge_id, validator_id = %caller.data().validator_id, function = enforcement, error = %err, "host memory read failed");
609638
return write_result::<HttpResponse>(
610639
caller,
611640
resp_ptr,
612641
resp_len,
613642
Err(NetworkError::HttpFailure(err)),
614-
)
643+
);
615644
}
616645
};
617646

618647
let request = match bincode::deserialize::<HttpPostRequest>(&request_bytes) {
619648
Ok(req) => req,
620649
Err(err) => {
650+
warn!(challenge_id = %caller.data().challenge_id, validator_id = %caller.data().validator_id, function = enforcement, error = %err, "host request decode failed");
621651
return write_result::<HttpResponse>(
622652
caller,
623653
resp_ptr,
624654
resp_len,
625655
Err(NetworkError::HttpFailure(format!(
626656
"invalid http post payload: {err}"
627657
))),
628-
)
658+
);
629659
}
630660
};
631661

@@ -637,6 +667,9 @@ fn handle_http_post(
637667
};
638668

639669
let result = caller.data_mut().network_state.handle_http_request(request);
670+
if let Err(ref err) = result {
671+
warn!(challenge_id = %caller.data().challenge_id, validator_id = %caller.data().validator_id, function = enforcement, error = %err, "host request denied");
672+
}
640673
write_result(caller, resp_ptr, resp_len, result)
641674
}
642675

@@ -647,33 +680,39 @@ fn handle_dns_request(
647680
resp_ptr: i32,
648681
resp_len: i32,
649682
) -> i32 {
683+
let enforcement = "dns_resolve";
650684
let request_bytes = match read_memory(caller, req_ptr, req_len) {
651685
Ok(bytes) => bytes,
652686
Err(err) => {
687+
warn!(challenge_id = %caller.data().challenge_id, validator_id = %caller.data().validator_id, function = enforcement, error = %err, "host memory read failed");
653688
return write_result::<DnsResponse>(
654689
caller,
655690
resp_ptr,
656691
resp_len,
657692
Err(NetworkError::DnsFailure(err)),
658-
)
693+
);
659694
}
660695
};
661696

662697
let request = match bincode::deserialize::<DnsRequest>(&request_bytes) {
663698
Ok(req) => req,
664699
Err(err) => {
700+
warn!(challenge_id = %caller.data().challenge_id, validator_id = %caller.data().validator_id, function = enforcement, error = %err, "host request decode failed");
665701
return write_result::<DnsResponse>(
666702
caller,
667703
resp_ptr,
668704
resp_len,
669705
Err(NetworkError::DnsFailure(format!(
670706
"invalid dns request payload: {err}"
671707
))),
672-
)
708+
);
673709
}
674710
};
675711

676712
let result = caller.data_mut().network_state.handle_dns_request(request);
713+
if let Err(ref err) = result {
714+
warn!(challenge_id = %caller.data().challenge_id, validator_id = %caller.data().validator_id, function = enforcement, error = %err, "host request denied");
715+
}
677716
write_result(caller, resp_ptr, resp_len, result)
678717
}
679718

crates/wasm-runtime-interface/src/runtime.rs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::{NetworkPolicy, NetworkState};
1+
use crate::{NetworkAuditLogger, NetworkPolicy, NetworkState};
22
use std::sync::Arc;
33
use thiserror::Error;
44
use tracing::info;
@@ -73,39 +73,64 @@ impl Default for RuntimeConfig {
7373

7474
#[derive(Clone)]
7575
pub struct InstanceConfig {
76+
/// Network policy enforced by host functions.
7677
pub network_policy: NetworkPolicy,
78+
/// Optional audit logger for network calls.
79+
pub audit_logger: Option<Arc<dyn NetworkAuditLogger>>,
80+
/// Wasm memory export name.
7781
pub memory_export: String,
82+
/// Identifier used in audit logs.
7883
pub challenge_id: String,
84+
/// Validator identifier used in audit logs.
7985
pub validator_id: String,
86+
/// Restartable configuration identifier.
87+
pub restart_id: String,
88+
/// Configuration version for hot-restarts.
89+
pub config_version: u64,
8090
}
8191

8292
impl Default for InstanceConfig {
8393
fn default() -> Self {
8494
Self {
8595
network_policy: NetworkPolicy::default(),
96+
audit_logger: None,
8697
memory_export: DEFAULT_WASM_MEMORY_NAME.to_string(),
8798
challenge_id: "unknown".to_string(),
8899
validator_id: "unknown".to_string(),
100+
restart_id: String::new(),
101+
config_version: 0,
89102
}
90103
}
91104
}
92105

93106
pub struct RuntimeState {
107+
/// Network policy available to host functions.
94108
pub network_policy: NetworkPolicy,
109+
/// Mutable network state enforcing policy.
95110
pub network_state: NetworkState,
111+
/// Wasm memory export name.
96112
pub memory_export: String,
113+
/// Identifier used in audit logs.
97114
pub challenge_id: String,
115+
/// Validator identifier used in audit logs.
98116
pub validator_id: String,
117+
/// Restartable configuration identifier.
118+
pub restart_id: String,
119+
/// Configuration version for hot-restarts.
120+
pub config_version: u64,
99121
limits: StoreLimits,
100122
}
101123

102124
impl RuntimeState {
125+
#[allow(clippy::too_many_arguments)]
103126
pub fn new(
104127
network_policy: NetworkPolicy,
105128
network_state: NetworkState,
106129
memory_export: String,
107130
challenge_id: String,
108131
validator_id: String,
132+
restart_id: String,
133+
config_version: u64,
109134
limits: StoreLimits,
110135
) -> Self {
111136
Self {
@@ -114,6 +139,8 @@ impl RuntimeState {
114139
memory_export,
115140
challenge_id,
116141
validator_id,
142+
restart_id,
143+
config_version,
117144
limits,
118145
}
119146
}
@@ -180,7 +207,7 @@ impl WasmRuntime {
180207
limits = limits.instances(self.config.max_instances as usize);
181208
let network_state = NetworkState::new(
182209
instance_config.network_policy.clone(),
183-
None,
210+
instance_config.audit_logger.clone(),
184211
instance_config.challenge_id.clone(),
185212
instance_config.validator_id.clone(),
186213
)
@@ -191,6 +218,8 @@ impl WasmRuntime {
191218
instance_config.memory_export.clone(),
192219
instance_config.challenge_id.clone(),
193220
instance_config.validator_id.clone(),
221+
instance_config.restart_id.clone(),
222+
instance_config.config_version,
194223
limits.build(),
195224
);
196225
let mut store = Store::new(&self.engine, runtime_state);

0 commit comments

Comments
 (0)