warg_server/policy/content/
wasm.rs1use super::{ContentPolicy, ContentPolicyError, ContentPolicyResult, ContentStreamPolicy};
2use warg_crypto::hash::AnyHash;
3use wasmparser::{
4 Chunk, Encoding, FuncValidatorAllocations, Parser, ValidPayload, Validator, WasmFeatures,
5};
6
7pub struct WasmContentPolicy {
9 allow_modules: bool,
10 allow_components: bool,
11}
12
13impl WasmContentPolicy {
14 pub fn new() -> Self {
16 Self::default()
17 }
18
19 pub fn disallow_modules(mut self) -> Self {
21 self.allow_modules = false;
22 self
23 }
24
25 pub fn disallow_components(mut self) -> Self {
27 self.allow_components = false;
28 self
29 }
30}
31
32impl Default for WasmContentPolicy {
33 fn default() -> Self {
34 Self {
35 allow_modules: true,
36 allow_components: true,
37 }
38 }
39}
40
41impl ContentPolicy for WasmContentPolicy {
42 fn new_stream_policy(
43 &self,
44 _digest: &AnyHash,
45 ) -> ContentPolicyResult<Box<dyn ContentStreamPolicy>> {
46 Ok(Box::new(WasmContentStreamPolicy {
47 buffer: Vec::new(),
48 parser: Parser::new(0),
49 stack: Vec::new(),
50 validator: wasmparser::Validator::new_with_features(WasmFeatures::default()),
51 allocs: FuncValidatorAllocations::default(),
52 allow_modules: self.allow_modules,
53 allow_components: self.allow_components,
54 }))
55 }
56}
57
58struct WasmContentStreamPolicy {
59 buffer: Vec<u8>,
60 parser: Parser,
61 stack: Vec<Parser>,
62 validator: Validator,
63 allocs: FuncValidatorAllocations,
64 allow_modules: bool,
65 allow_components: bool,
66}
67
68impl WasmContentStreamPolicy {
69 fn process(&mut self, bytes: &[u8], eof: bool) -> ContentPolicyResult<()> {
70 let buf = if !self.buffer.is_empty() {
72 self.buffer.extend(bytes);
73 self.buffer.as_slice()
74 } else {
75 bytes
76 };
77
78 let mut offset = 0;
79 loop {
80 let (payload, consumed) = match self.parser.parse(&buf[offset..], eof).map_err(|e| {
81 ContentPolicyError::Rejection(format!("content is not valid WebAssembly: {e}"))
82 })? {
83 Chunk::NeedMoreData(_) => {
84 if self.buffer.is_empty() && offset < bytes.len() {
90 self.buffer.extend_from_slice(&bytes[offset..]);
91 } else if offset < self.buffer.len() {
92 self.buffer.copy_within(offset.., 0);
93 self.buffer.truncate(self.buffer.len() - offset);
94 } else {
95 self.buffer.clear();
96 }
97 return Ok(());
98 }
99
100 Chunk::Parsed { consumed, payload } => (payload, consumed),
101 };
102
103 offset += consumed;
104
105 match &payload {
106 wasmparser::Payload::Version {
107 encoding: Encoding::Module,
108 ..
109 } if !self.allow_modules => {
110 return Err(ContentPolicyError::Rejection(
111 "WebAssembly modules are not allowed".to_string(),
112 ))
113 }
114 wasmparser::Payload::Version {
115 encoding: Encoding::Component,
116 ..
117 } if !self.allow_components => {
118 return Err(ContentPolicyError::Rejection(
119 "WebAssembly components are not allowed".to_string(),
120 ))
121 }
122 _ => {}
123 }
124
125 match self.validator.payload(&payload).map_err(|e| {
126 ContentPolicyError::Rejection(format!("content is not valid WebAssembly: {e}"))
127 })? {
128 ValidPayload::Ok => {}
129 ValidPayload::Parser(p) => {
130 self.stack.push(self.parser.clone());
131 self.parser = p;
132 }
133 ValidPayload::Func(func, body) => {
134 let allocs = std::mem::take(&mut self.allocs);
135 let mut validator = func.into_validator(allocs);
136 validator.validate(&body).map_err(|e| {
137 ContentPolicyError::Rejection(format!(
138 "content is not valid WebAssembly: {e}"
139 ))
140 })?;
141 self.allocs = validator.into_allocations();
142 }
143 ValidPayload::End(_) => {
144 if let Some(parser) = self.stack.pop() {
145 self.parser = parser;
146 } else {
147 return Ok(());
148 }
149 }
150 }
151 }
152 }
153}
154
155impl ContentStreamPolicy for WasmContentStreamPolicy {
156 fn check(&mut self, bytes: &[u8]) -> ContentPolicyResult<()> {
157 self.process(bytes, false)
158 }
159
160 fn finalize(&mut self) -> ContentPolicyResult<()> {
161 self.process(&[], true)
162 }
163}