-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathDifferentialFuzzProtocolWitness.php
More file actions
126 lines (111 loc) · 3.8 KB
/
Copy pathDifferentialFuzzProtocolWitness.php
File metadata and controls
126 lines (111 loc) · 3.8 KB
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
<?php
declare(strict_types=1);
namespace FediE2EE\PKDServer\Fuzzing;
use FediE2EE\PKDServer\Protocol;
use FediE2EE\PKDServer\Scheduled\Witness;
use FediE2EE\PKDServer\Tables\Records\Peer;
use PhpFuzzer\Config;
use ReflectionClass;
use ReflectionException;
use ReflectionMethod;
use RuntimeException;
use Throwable;
use function
array_diff,
array_merge,
array_slice,
array_unique,
array_values,
count,
dirname,
file,
implode,
is_array,
json_decode,
ord,
sort,
strlen,
substr;
/** @var Config $config */
require_once dirname(__DIR__) . '/vendor/autoload.php';
/**
* @return string[]
*
* @throws ReflectionException
*/
function extractMatchActions(string $className, string $methodName): array
{
$ref = new ReflectionMethod($className, $methodName);
$file = $ref->getFileName();
$start = $ref->getStartLine();
$end = $ref->getEndLine();
if ($file === false || $start === false || $end === false) {
return [];
}
$lines = file($file);
if ($lines === false) {
return [];
}
$source = implode('', array_slice($lines, $start - 1, $end - $start + 1));
preg_match_all("/['\"]([A-Z][a-zA-Z]+)['\"]\s*=>/", $source, $matches);
return array_values(array_unique($matches[1] ?? []));
}
// --- Static divergence check ---
$protocolEncrypted = extractMatchActions(Protocol::class, 'routeEncryptedAction');
$protocolPlaintext = extractMatchActions(Protocol::class, 'routePlaintextAction');
$protocolAll = array_values(array_unique(array_merge($protocolEncrypted, $protocolPlaintext)));
sort($protocolAll);
$witnessActions = extractMatchActions(Witness::class, 'processReplicatedAction');
sort($witnessActions);
// RevokeKeyThirdParty enters Protocol via a separate endpoint (not process()),
// so it's expected in Witness but absent from Protocol's match dispatch.
$protocolForComparison = array_merge($protocolAll, ['RevokeKeyThirdParty']);
sort($protocolForComparison);
$missingInWitness = array_diff($protocolForComparison, $witnessActions);
$missingInProtocol = array_diff($witnessActions, $protocolForComparison);
if (!empty($missingInWitness)) {
throw new RuntimeException(
'Actions in Protocol but not Witness: ' . implode(', ', $missingInWitness)
);
}
if (!empty($missingInProtocol)) {
throw new RuntimeException(
'Actions in Witness but not Protocol: ' . implode(', ', $missingInProtocol)
);
}
// --- Dynamic fuzzing of Witness.processReplicatedAction ---
// Feed random action strings + message arrays to ensure
// no unexpected crashes on malformed replication data.
$witness = new Witness($GLOBALS['pkdConfig']);
$reflectedMethod = new ReflectionMethod(Witness::class, 'processReplicatedAction');
$reflectedMethod->setAccessible(true);
// Build a minimal Peer for invocations
$peerRef = new ReflectionClass(Peer::class);
$config->setTarget(function (string $input) use ($witness, $reflectedMethod, $witnessActions): void {
// Split fuzz input: first byte selects action, rest is JSON body
if (strlen($input) < 2) {
return;
}
$actionIdx = ord($input[0]) % (count($witnessActions) + 1);
$jsonPart = substr($input, 1);
// Either pick a known action or use raw bytes as action name
$action = $actionIdx < count($witnessActions)
? $witnessActions[$actionIdx]
: $jsonPart;
$message = @json_decode($jsonPart, true);
if (!is_array($message)) {
$message = ['actor' => $jsonPart];
}
try {
$reflectedMethod->invoke(
$witness,
null, // peer — null triggers early returns in process* methods
$action,
$message,
0 // leafId
);
} catch (Throwable) {
// Expected for malformed inputs; we're looking for
// unexpected fatal errors or segfaults, not exceptions.
}
});