Repository navigation
Socket syscall fault injection for net/tls/http/http2/fetch/WebSocket fuzzing + 7 bug fixes #32681
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
a3e02ba
Socket syscall fault injection for net/tls/http/http2/fetch/WebSocket…
cirospaciari 8a15f5c
Address review feedback on socket fault injection
cirospaciari d77bf0e
Add safety comments required by clippy on the new unsafe blocks
cirospaciari ca14405
Share the writable drain logic between the socket and tunnel paths
cirospaciari f2e2ae3
Bounds-check the syscall id in us_fault_hit like the other accessors
cirospaciari 5f8e7db
Reject "zero" fault rules for syscalls with no zero-return semantics
cirospaciari a2cd8c1
Normalize epoll EPOLLERR to 0/1 and tighten fault rule validation
cirospaciari bd40151
Report the real socket error when a poll error closes the socket
cirospaciari 6e11400
Keep the close-code errno invariant airtight for the poll-error path
cirospaciari File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| #include "internal/internal.h" | ||
| #include "internal/fault_inject.h" | ||
|
|
||
| #if defined(LIBUS_SOCKET_FAULT_INJECTION) && LIBUS_SOCKET_FAULT_INJECTION | ||
|
|
||
| #include <errno.h> | ||
| #include <string.h> | ||
|
|
||
| int us_fault_armed = 0; | ||
|
|
||
| struct us_fault_slot { | ||
| struct us_fault_rule rule; | ||
| int calls_seen; | ||
| int fired; | ||
| }; | ||
|
|
||
| /* Process-global so rules armed on the JS thread also affect the HTTP-client | ||
| * thread (fetch) and any worker threads. Per-socket isolation is provided by | ||
| * rule.target_fd instead. */ | ||
| static struct us_fault_slot us_fault_state[US_FAULT_COUNT]; | ||
|
|
||
| /* Guards every access to us_fault_state so a re-arm on the JS thread cannot | ||
| * tear the rule (or its counters) out from under a faulting I/O thread. Only | ||
| * taken once the lock-free us_fault_armed fast path in US_FAULT_CHECK has | ||
| * passed, so the disarmed hot path never touches it. */ | ||
| static zig_mutex_t us_fault_lock; | ||
|
|
||
| /* Caller must hold us_fault_lock. The release store pairs with the acquire | ||
| * load in US_FAULT_CHECK; a reader that observes armed==1 then re-reads the | ||
| * rule under the lock in us_fault_hit(), so it never sees a torn rule. */ | ||
| static void us_fault_recompute_armed(void) { | ||
| int any = 0; | ||
| for (int i = 0; i < US_FAULT_COUNT; i++) { | ||
| if (us_fault_state[i].rule.action != US_FAULT_NONE) { | ||
| any = 1; | ||
| break; | ||
| } | ||
| } | ||
| __atomic_store_n(&us_fault_armed, any, __ATOMIC_RELEASE); | ||
| } | ||
|
|
||
| void us_fault_set(int sc, const struct us_fault_rule *rule) { | ||
| if ((unsigned)sc >= US_FAULT_COUNT) return; | ||
| Bun__lock(&us_fault_lock); | ||
| us_fault_state[sc].rule = *rule; | ||
| us_fault_state[sc].calls_seen = 0; | ||
| us_fault_state[sc].fired = 0; | ||
| us_fault_recompute_armed(); | ||
|
cirospaciari marked this conversation as resolved.
|
||
| Bun__unlock(&us_fault_lock); | ||
| } | ||
|
|
||
| void us_fault_clear(int sc) { | ||
| if ((unsigned)sc >= US_FAULT_COUNT) return; | ||
| Bun__lock(&us_fault_lock); | ||
| us_fault_state[sc].rule.action = US_FAULT_NONE; | ||
| us_fault_state[sc].calls_seen = 0; | ||
| us_fault_state[sc].fired = 0; | ||
| us_fault_recompute_armed(); | ||
| Bun__unlock(&us_fault_lock); | ||
| } | ||
|
|
||
| void us_fault_clear_all(void) { | ||
| Bun__lock(&us_fault_lock); | ||
| for (int i = 0; i < US_FAULT_COUNT; i++) { | ||
| us_fault_state[i].rule.action = US_FAULT_NONE; | ||
| } | ||
| __atomic_store_n(&us_fault_armed, 0, __ATOMIC_RELEASE); | ||
| Bun__unlock(&us_fault_lock); | ||
| } | ||
|
|
||
| int us_fault_hit(int sc, int fd, ssize_t *out, int *clamp) { | ||
| if ((unsigned)sc >= US_FAULT_COUNT) return 0; | ||
| Bun__lock(&us_fault_lock); | ||
| struct us_fault_slot *slot = &us_fault_state[sc]; | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| /* Snapshot under the lock so the post-release switch below acts on one | ||
| * coherent rule even if another thread swaps it right after we unlock. | ||
| * Single lock/unlock pair: every exit funnels through the one release. */ | ||
| struct us_fault_rule rule = slot->rule; | ||
| int fire = 0; | ||
| if (rule.action != US_FAULT_NONE && (rule.target_fd < 0 || rule.target_fd == fd)) { | ||
| int seen = slot->calls_seen++; | ||
| if (seen >= rule.after_n_calls) { | ||
| int f = slot->fired++; | ||
| if (rule.repeat >= 0 && f >= rule.repeat) { | ||
| slot->rule.action = US_FAULT_NONE; | ||
| us_fault_recompute_armed(); | ||
| } else { | ||
| fire = 1; | ||
| } | ||
| } | ||
| } | ||
| Bun__unlock(&us_fault_lock); | ||
| if (!fire) return 0; | ||
| switch (rule.action) { | ||
| case US_FAULT_ERRNO: | ||
| #ifdef _WIN32 | ||
| WSASetLastError(rule.errno_value); | ||
| #endif | ||
| errno = rule.errno_value; | ||
| *out = -1; | ||
| return 1; | ||
| case US_FAULT_ZERO: | ||
| *out = 0; | ||
| return 1; | ||
| case US_FAULT_SHORT: | ||
| if (rule.clamp_bytes >= 0 && *clamp > rule.clamp_bytes) { | ||
| *clamp = rule.clamp_bytes; | ||
| } | ||
| return 0; | ||
| default: | ||
| return 0; | ||
| } | ||
| } | ||
|
|
||
| #endif /* LIBUS_SOCKET_FAULT_INJECTION */ | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| /* | ||
| * Socket syscall fault injection for the bsd_* wrapper layer. | ||
| * | ||
| * Compiled in only when LIBUS_SOCKET_FAULT_INJECTION is defined (controlled | ||
| * by the `socketFaultInjection` Config field). When compiled out, | ||
| * US_FAULT_CHECK() expands to a constant 0 and the optimizer drops every | ||
| * reference. When compiled in but no rule is armed, the hot path is a single | ||
| * acquire atomic load + predicted-not-taken branch. | ||
| * | ||
| * Rules are process-global so a rule armed from the JS thread also affects | ||
| * the HTTP-client thread (fetch) and worker threads. Use rule.target_fd for | ||
| * per-socket isolation. | ||
| */ | ||
| // clang-format off | ||
| #pragma once | ||
| #ifndef LIBUS_FAULT_INJECT_H | ||
| #define LIBUS_FAULT_INJECT_H | ||
|
|
||
| #if defined(LIBUS_SOCKET_FAULT_INJECTION) && LIBUS_SOCKET_FAULT_INJECTION | ||
|
|
||
| #ifdef _WIN32 | ||
| #include <BaseTsd.h> | ||
| typedef SSIZE_T ssize_t; | ||
| #else | ||
| #include <sys/types.h> | ||
| #endif | ||
|
|
||
| enum us_fault_syscall { | ||
| US_FAULT_RECV, | ||
| US_FAULT_SEND, | ||
| US_FAULT_WRITEV, | ||
| US_FAULT_SENDMSG, | ||
| US_FAULT_RECVMSG, | ||
| US_FAULT_CONNECT, | ||
| US_FAULT_ACCEPT, | ||
| /* Reserved: no bsd.c hooks yet, so the JS setter does not accept them. */ | ||
| US_FAULT_SOCKET, | ||
| US_FAULT_CLOSE, | ||
| US_FAULT_SHUTDOWN, | ||
| US_FAULT_COUNT | ||
| }; | ||
|
|
||
| enum us_fault_action { | ||
| US_FAULT_NONE, | ||
| /* return -1 and set errno = errno_value */ | ||
| US_FAULT_ERRNO, | ||
| /* recv/send: clamp the length to clamp_bytes, then run the real syscall. | ||
| * Other syscalls have no length to clamp; the JS setter rejects them. */ | ||
| US_FAULT_SHORT, | ||
| /* recv/recvmsg: return 0 (peer closed); send/sendmsg/writev: return 0 | ||
| * (treated as backpressure). The JS setter rejects other syscalls. */ | ||
| US_FAULT_ZERO, | ||
| }; | ||
|
|
||
| struct us_fault_rule { | ||
| int action; | ||
| int errno_value; | ||
| int clamp_bytes; | ||
| /* skip the first N matching calls before triggering */ | ||
| int after_n_calls; | ||
| /* fire this many times then disarm; -1 = forever */ | ||
| int repeat; | ||
| /* match only this fd; -1 = any */ | ||
| int target_fd; | ||
| }; | ||
|
|
||
| #ifdef __cplusplus | ||
| extern "C" { | ||
| #endif | ||
|
|
||
| extern int us_fault_armed; | ||
|
|
||
| void us_fault_set(int syscall, const struct us_fault_rule *rule); | ||
| void us_fault_clear(int syscall); | ||
| void us_fault_clear_all(void); | ||
| int us_fault_hit(int syscall, int fd, ssize_t *out, int *clamp); | ||
|
|
||
| #ifdef __cplusplus | ||
| } | ||
| #endif | ||
|
|
||
| /* Hot-path macro. `out_lvalue` and `clamp_lvalue` must be lvalues. Returns 1 | ||
| * when the call should short-circuit with *out_lvalue. clamp_lvalue may be | ||
| * shrunk in-place when the rule wants a partial read/write. */ | ||
| #define US_FAULT_CHECK(sc, fd, out_lvalue, clamp_lvalue) \ | ||
| (__builtin_expect(__atomic_load_n(&us_fault_armed, __ATOMIC_ACQUIRE), 0) \ | ||
| && us_fault_hit((sc), (int)(fd), &(out_lvalue), &(clamp_lvalue))) | ||
|
|
||
| #else /* !LIBUS_SOCKET_FAULT_INJECTION */ | ||
|
|
||
| #define US_FAULT_CHECK(sc, fd, out_lvalue, clamp_lvalue) 0 | ||
|
|
||
| #endif | ||
| #endif /* LIBUS_FAULT_INJECT_H */ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.