1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
Aspect oriented programming in a safe/predictable way
=====================================================
Unlimited Aspect Oriented Programming seems to be quite risky, and also
hard to optimize (because anything can be changed).
But there are some alternative methods:
* Compile-time checks of behavior (rather than altering behavior).
* Symbol interposition / proxying (when there are cross-module calls, since
intra-module calls might lead to too much of a performance degradation).
And some additional restrictions:
* Forbid visible behavior changes of the code being "modified":
- Allow "passive" operations like logging
- Allow checks, where the result is either to continue or to
abort the "task".
* Arena lifetime/capability restrictions must be respected:
- The arena of the "aspect code" must be an "outer" arena and the
"modified code" must be an "inner" arena.
With these restrictions, it would be possible to add for example:
* Logging
* Debug tracing
* Profiling
* Security checks (e.g. of HTTP headers)
* Constraint enforcement (e.g. after function `f` is called, then `g` may
not be called with the same parameter until `h` has been called)
How to implement the proxying?
------------------------------
It would be nice if:
* It is "zero-cost" when not used.
* It does not add exploit gadgets, e.g. function pointers, unless that
is really necessary.
* It is somewhat performant (not 10x slower).
Maybe it could be implemented as an optional buffer in the arena. If present,
then function calls are serialized and logged to the buffer. Before the
task has externally visible effects (e.g. I/O) and at repeated points (e.g.
in long loops), the contents of the buffer is processed and can abort the
task.
The per-function check could be essentially:
if (arena->aspectinfo && hash_present(arena->aspect_bloom_filter, funchash)) {
queue_or_process_aspect(arena->aspectinfo, [args1, arg2, ...])
}
I/O calls themselves could also be proxied. This could be done to implement
a "dry-run mode", for example.
|