Skip to content

Commit 3e48525

Browse files
committed
Describes the forking behaviour of scannerctl/openvasd
1 parent e6abbc2 commit 3e48525

File tree

1 file changed

+144
-0
lines changed

1 file changed

+144
-0
lines changed

rust/doc/faq/forking.md

+144
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
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

Comments
 (0)