|
| 1 | +# Simulated in NASL |
| 2 | + |
| 3 | +In NASL scripts, functions like get_kb_item() and open_sock_tcp() simulate process forking, meaning they transparently start additional script executions. |
| 4 | + |
| 5 | +For exmaple: |
| 6 | +```nasl |
| 7 | +set_kb_item(name: "test", value: 1); |
| 8 | +set_kb_item(name: "test", value: 2); |
| 9 | +set_kb_item(name: "test", value: 3); |
| 10 | +set_kb_item(name: "test", value: 4); |
| 11 | +set_kb_item(name: "test", value: 5); |
| 12 | +display(get_kb_item("test")); |
| 13 | +display('hi'); |
| 14 | +``` |
| 15 | + |
| 16 | +With scannerctl, the script runs sequentially, displaying results one by one: |
| 17 | +```bash |
| 18 | +> scannerctl execute script get_kb_item.nasl |
| 19 | +5 |
| 20 | +1 |
| 21 | +2 |
| 22 | +3 |
| 23 | +4 |
| 24 | +hi |
| 25 | +hi |
| 26 | +hi |
| 27 | +hi |
| 28 | +hi |
| 29 | +``` |
| 30 | + |
| 31 | +In contrast, openvas-nasl forks the script, executing statements in parallel: |
| 32 | + |
| 33 | +```bash |
| 34 | +> openvas-nasl -X get_kb_item.nasl |
| 35 | +** WARNING : packet forgery will not work |
| 36 | +** as NASL is not running as root |
| 37 | +lib nasl-Message: 13:34:38.456: 5 |
| 38 | +lib nasl-Message: 13:34:38.456: hi |
| 39 | +lib nasl-Message: 13:34:38.458: 4 |
| 40 | +lib nasl-Message: 13:34:38.458: hi |
| 41 | +lib nasl-Message: 13:34:38.460: 3 |
| 42 | +lib nasl-Message: 13:34:38.460: hi |
| 43 | +lib nasl-Message: 13:34:38.461: 2 |
| 44 | +lib nasl-Message: 13:34:38.461: hi |
| 45 | +lib nasl-Message: 13:34:38.463: 1 |
| 46 | +lib nasl-Message: 13:34:38.463: hi |
| 47 | +lib nasl-Message: 13:34:38.464: |
| 48 | +lib nasl-Message: 13:34:38.464: hi |
| 49 | +``` |
| 50 | + |
| 51 | +The `scannerctl` approach is memory-efficient, storing only active registry results, while `openvas-nasl` allows parallel execution by duplicating execution paths. |
| 52 | + |
| 53 | +## Developing Builtin Functions with Forking |
| 54 | + |
| 55 | +To simulate forking in a NASL builtin function, developers should return `NaslValue::Fork`, which holds a `Vec` of `NaslValues` for separate execution paths. For example: |
| 56 | + |
| 57 | +```rust |
| 58 | +/// NASL function to get a knowledge base |
| 59 | +#[nasl_function] |
| 60 | +fn get_kb_item(c: &Context, key: &str) -> Result<NaslValue, FunctionErrorKind> { |
| 61 | + c.retriever() |
| 62 | + .retrieve(c.key(), Retrieve::KB(key.to_string())) |
| 63 | + .map(|r| { |
| 64 | + r.into_iter() |
| 65 | + .filter_map(|x| match x { |
| 66 | + Field::NVT(_) | Field::NotusAdvisory(_) | Field::Result(_) => None, |
| 67 | + Field::KB(kb) => Some(kb.value.into()), |
| 68 | + }) |
| 69 | + .collect::<Vec<_>>() |
| 70 | + }) |
| 71 | + .map(NaslValue::Fork) |
| 72 | + .map_err(|e| e.into()) |
| 73 | +} |
| 74 | +``` |
| 75 | + |
| 76 | +## Internal Handling of Forking |
| 77 | +The interpreter checks if a function is called from the main script (index 0). If so, it creates new execution blocks for each NaslValue::Fork entry, cloning the registry and tracking the position to avoid re-running statements. |
| 78 | + |
| 79 | + |
| 80 | +```rust |
| 81 | +let result = match self.ctxconfigs.nasl_fn_execute(name, self.register()).await { |
| 82 | + Some(r) => { |
| 83 | + if let Ok(NaslValue::Fork(mut x)) = r { |
| 84 | + Ok(if let Some(r) = x.pop() { |
| 85 | + // this is a proposal for the case that the caller is immediately executing |
| 86 | + // if not the position needs to be reset |
| 87 | + if self.index == 0 { |
| 88 | + let position = self.position().current_init_statement(); |
| 89 | + for i in x { |
| 90 | + tracing::trace!(return_value=?i, return_position=?self.position(), interpreter_position=?position, "creating interpreter instance" ); |
| 91 | + self.run_specific.push(RunSpecific { |
| 92 | + register: self.register().clone(), |
| 93 | + position: position.clone(), |
| 94 | + skip_until_return: Some((self.position().clone(), i)), |
| 95 | + }); |
| 96 | + } |
| 97 | + } else { |
| 98 | + tracing::trace!( |
| 99 | + index = self.index, |
| 100 | + "we only allow expanding of executions (fork) on root instance" |
| 101 | + ); |
| 102 | + } |
| 103 | + tracing::trace!(return_value=?r, "returning interpreter instance" ); |
| 104 | + r |
| 105 | + } else { |
| 106 | + NaslValue::Null |
| 107 | + }) |
| 108 | + } else { |
| 109 | + r.map_err(|x| FunctionError::new(name, x).into()) |
| 110 | + } |
| 111 | + } |
| 112 | + ... |
| 113 | +} |
| 114 | +``` |
| 115 | + |
| 116 | +Each interpreter instance retrieves the stored NaslValue from skip_until_return before proceeding with the script: |
| 117 | + |
| 118 | +```rust |
| 119 | +/// Evaluates the next statement |
| 120 | +pub async fn next_statement(&mut self) -> Option<InterpretResult> { |
| 121 | + self.statement = None; |
| 122 | + match self.lexer.next() { |
| 123 | + Some(Ok(nstmt)) => { |
| 124 | + let results = Some(self.interpreter.retry_resolve_next(&nstmt, 5).await); |
| 125 | + self.statement = Some(nstmt); |
| 126 | + results |
| 127 | + } |
| 128 | + Some(Err(err)) => Some(Err(err.into())), |
| 129 | + None => None, |
| 130 | + } |
| 131 | +} |
| 132 | + |
| 133 | +async fn next_(&mut self) -> Option<InterpretResult> { |
| 134 | + if let Some(stmt) = self.statement.as_ref() { |
| 135 | + match self.interpreter.next_interpreter() { |
| 136 | + Some(inter) => Some(inter.retry_resolve(stmt, 5).await), |
| 137 | + None => self.next_statement().await, |
| 138 | + } |
| 139 | + } else { |
| 140 | + self.next_statement().await |
| 141 | + } |
| 142 | +} |
| 143 | +``` |
| 144 | + |
0 commit comments