-
Notifications
You must be signed in to change notification settings - Fork 3.9k
feat(op-wheel): add reth support for offline rewind and db inspection #20010
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
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,103 @@ | ||||||||
| package engine | ||||||||
|
|
||||||||
| import ( | ||||||||
| "context" | ||||||||
| "errors" | ||||||||
| "fmt" | ||||||||
| "os" | ||||||||
| "os/exec" | ||||||||
| "strconv" | ||||||||
|
|
||||||||
| "github.com/ethereum/go-ethereum/log" | ||||||||
| ) | ||||||||
|
|
||||||||
| // RethRewind performs an offline rewind of a reth node by executing | ||||||||
| // `reth stage unwind to-block <N>` as a subprocess. | ||||||||
| // The reth node must be stopped before calling this function. | ||||||||
| func RethRewind(ctx context.Context, lgr log.Logger, rethBinary string, datadir string, chain string, toBlock uint64) error { | ||||||||
| cmd, err := buildRethUnwindCmd(ctx, rethBinary, datadir, chain, toBlock) | ||||||||
| if err != nil { | ||||||||
| return err | ||||||||
| } | ||||||||
| return runRethCmd(ctx, lgr, cmd, "reth stage unwind") | ||||||||
| } | ||||||||
|
|
||||||||
| // RethState runs `reth db state <address>` to inspect account state offline. | ||||||||
| func RethState(ctx context.Context, lgr log.Logger, rethBinary string, datadir string, chain string, address string, block string, limit uint64) error { | ||||||||
| cmd, err := buildRethDBCmd(ctx, rethBinary, datadir, chain, "state", address, "--format", "json", "--limit", strconv.FormatUint(limit, 10)) | ||||||||
| if err != nil { | ||||||||
| return err | ||||||||
| } | ||||||||
| if block != "" { | ||||||||
| cmd.Args = append(cmd.Args, "--block", block) | ||||||||
| } | ||||||||
| return runRethCmd(ctx, lgr, cmd, "reth db state") | ||||||||
| } | ||||||||
|
|
||||||||
| // RethHead runs `reth db stage-checkpoints get` to show the current head (stage checkpoints). | ||||||||
| func RethHead(ctx context.Context, lgr log.Logger, rethBinary string, datadir string, chain string) error { | ||||||||
| cmd, err := buildRethDBCmd(ctx, rethBinary, datadir, chain, "stage-checkpoints", "get") | ||||||||
| if err != nil { | ||||||||
| return err | ||||||||
| } | ||||||||
| return runRethCmd(ctx, lgr, cmd, "reth db stage-checkpoints") | ||||||||
| } | ||||||||
|
|
||||||||
| // runRethCmd executes a reth command, streaming output and handling exit codes. | ||||||||
| func runRethCmd(_ context.Context, lgr log.Logger, cmd *exec.Cmd, label string) error { | ||||||||
| lgr.Info("Executing "+label, "binary", cmd.Path, "args", cmd.Args[1:]) | ||||||||
|
|
||||||||
| cmd.Stdout = os.Stdout | ||||||||
| cmd.Stderr = os.Stderr | ||||||||
|
|
||||||||
| if err := cmd.Run(); err != nil { | ||||||||
| var exitErr *exec.ExitError | ||||||||
| if errors.As(err, &exitErr) { | ||||||||
| return fmt.Errorf("%s failed with exit code %d: %w", label, exitErr.ExitCode(), err) | ||||||||
| } | ||||||||
| return fmt.Errorf("failed to execute reth: %w", err) | ||||||||
| } | ||||||||
|
|
||||||||
| lgr.Info(label + " completed successfully") | ||||||||
| return nil | ||||||||
| } | ||||||||
|
|
||||||||
| // resolveRethBinary validates the reth binary exists and returns its resolved path. | ||||||||
| func resolveRethBinary(rethBinary string) (string, error) { | ||||||||
| resolvedPath, err := exec.LookPath(rethBinary) | ||||||||
| if err != nil { | ||||||||
| return "", fmt.Errorf("reth binary not found at %q: %w", rethBinary, err) | ||||||||
| } | ||||||||
| return resolvedPath, nil | ||||||||
| } | ||||||||
|
|
||||||||
| // buildRethUnwindCmd constructs the exec.Cmd for `reth stage unwind to-block <N>`. | ||||||||
| func buildRethUnwindCmd(ctx context.Context, rethBinary string, datadir string, chain string, toBlock uint64) (*exec.Cmd, error) { | ||||||||
| resolvedPath, err := resolveRethBinary(rethBinary) | ||||||||
| if err != nil { | ||||||||
| return nil, err | ||||||||
| } | ||||||||
|
|
||||||||
| args := []string{ | ||||||||
| "stage", "unwind", | ||||||||
| "--datadir", datadir, | ||||||||
| "--chain", chain, | ||||||||
| "to-block", strconv.FormatUint(toBlock, 10), | ||||||||
| } | ||||||||
|
|
||||||||
| return exec.CommandContext(ctx, resolvedPath, args...), nil | ||||||||
| } | ||||||||
|
|
||||||||
| // buildRethDBCmd constructs an exec.Cmd for `reth db --datadir <dir> --chain <chain> <subcommand> [args...]`. | ||||||||
| func buildRethDBCmd(ctx context.Context, rethBinary string, datadir string, chain string, subArgs ...string) (*exec.Cmd, error) { | ||||||||
| resolvedPath, err := resolveRethBinary(rethBinary) | ||||||||
| if err != nil { | ||||||||
| return nil, err | ||||||||
| } | ||||||||
|
|
||||||||
| // reth db --datadir <dir> --chain <chain> <subcommand> [args...] | ||||||||
| args := []string{"db", "--datadir", datadir, "--chain", chain} | ||||||||
| args = append(args, subArgs...) | ||||||||
|
|
||||||||
| return exec.CommandContext(ctx, resolvedPath, args...), nil | ||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OS Command Injection (CWE-78) More DetailsOS command injection is a critical vulnerability that allows an attacker to execute arbitrary commands on the system. This can lead to a full system compromise, as the attacker can potentially gain complete control over the application and the underlying system.
RemediationTo remediate this vulnerability, follow these recommendations:
Example of safely executing an OS command: userData := []byte("user data")
// Create a temporary file in the application-specific directory
f, err := ioutil.TempFile("/var/app/restricted", "temp-*.dat")
if err != nil {
log.Fatal(err)
}
if _, err := f.Write(userData); err != nil {
log.Fatal(err)
}
if err := f.Close(); err != nil {
log.Fatal(err)
}
// Pass the full path to the binary and the name of the temporary file
out, err := exec.Command("/bin/cat", f.Name()).Output()
if err != nil {
log.Fatal(err)
}
To ignore this finding as an exception, reply to this conversation with If you'd like to ignore this finding in all future scans, add an exception in the .wiz file (learn more) or create an Ignore Rule (learn more). To get more details on how to remediate this issue using AI, reply to this conversation with |
||||||||
| } | ||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,132 @@ | ||
| package engine | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "os" | ||
| "os/exec" | ||
| "strconv" | ||
| "testing" | ||
|
|
||
| "github.com/ethereum/go-ethereum/log" | ||
| ) | ||
|
|
||
| func TestBuildRethUnwindCmd_Args(t *testing.T) { | ||
| self, err := os.Executable() | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
|
|
||
| cmd, err := buildRethUnwindCmd(context.Background(), self, "/data/reth", "op-mainnet", 12345678) | ||
| if err != nil { | ||
| t.Fatalf("unexpected error: %v", err) | ||
| } | ||
|
|
||
| wantArgs := []string{ | ||
| self, | ||
| "stage", "unwind", | ||
| "--datadir", "/data/reth", | ||
| "--chain", "op-mainnet", | ||
| "to-block", "12345678", | ||
| } | ||
| assertArgs(t, cmd.Args, wantArgs) | ||
| } | ||
|
|
||
| func TestBuildRethUnwindCmd_BinaryNotFound(t *testing.T) { | ||
| _, err := buildRethUnwindCmd(context.Background(), "/nonexistent/reth", "/data", "optimism", 100) | ||
| if err == nil { | ||
| t.Fatal("expected error for nonexistent binary, got nil") | ||
| } | ||
| } | ||
|
|
||
| func TestBuildRethDBCmd_State(t *testing.T) { | ||
| self, err := os.Executable() | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
|
|
||
| cmd, err := buildRethDBCmd(context.Background(), self, "/db", "optimism", | ||
| "state", "0xdead", "--format", "json", "--limit", "100") | ||
| if err != nil { | ||
| t.Fatalf("unexpected error: %v", err) | ||
| } | ||
|
|
||
| wantArgs := []string{ | ||
| self, | ||
| "db", "--datadir", "/db", "--chain", "optimism", | ||
| "state", "0xdead", "--format", "json", "--limit", "100", | ||
| } | ||
| assertArgs(t, cmd.Args, wantArgs) | ||
| } | ||
|
|
||
| func TestBuildRethDBCmd_StageCheckpoints(t *testing.T) { | ||
| self, err := os.Executable() | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
|
|
||
| cmd, err := buildRethDBCmd(context.Background(), self, "/db", "dev", | ||
| "stage-checkpoints", "get") | ||
| if err != nil { | ||
| t.Fatalf("unexpected error: %v", err) | ||
| } | ||
|
|
||
| wantArgs := []string{ | ||
| self, | ||
| "db", "--datadir", "/db", "--chain", "dev", | ||
| "stage-checkpoints", "get", | ||
| } | ||
| assertArgs(t, cmd.Args, wantArgs) | ||
| } | ||
|
|
||
| func TestRethRewind_SubprocessExit(t *testing.T) { | ||
| if os.Getenv("GO_TEST_HELPER_PROCESS") == "1" { | ||
| code, _ := strconv.Atoi(os.Getenv("GO_TEST_EXIT_CODE")) | ||
| os.Exit(code) | ||
| } | ||
|
|
||
| self, err := os.Executable() | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| lgr := log.NewLogger(log.DiscardHandler()) | ||
|
|
||
| t.Run("success", func(t *testing.T) { | ||
| err := rethCmdWithHelper(context.Background(), lgr, self, 0, "test") | ||
| if err != nil { | ||
| t.Fatalf("expected success, got: %v", err) | ||
| } | ||
| }) | ||
|
|
||
| t.Run("failure", func(t *testing.T) { | ||
| err := rethCmdWithHelper(context.Background(), lgr, self, 1, "test") | ||
| if err == nil { | ||
| t.Fatal("expected error for exit code 1, got nil") | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| // rethCmdWithHelper runs a subprocess using the test binary as a fake reth, | ||
| // configured to exit with the given code. | ||
| func rethCmdWithHelper(ctx context.Context, lgr log.Logger, testBinary string, exitCode int, label string) error { | ||
| cmd := exec.CommandContext(ctx, testBinary, | ||
| "-test.run=TestRethRewind_SubprocessExit", | ||
| ) | ||
| cmd.Env = append(os.Environ(), | ||
| "GO_TEST_HELPER_PROCESS=1", | ||
| fmt.Sprintf("GO_TEST_EXIT_CODE=%d", exitCode), | ||
| ) | ||
| return runRethCmd(ctx, lgr, cmd, label) | ||
| } | ||
|
|
||
| func assertArgs(t *testing.T, got, want []string) { | ||
| t.Helper() | ||
| if len(got) != len(want) { | ||
| t.Fatalf("args length mismatch: got %d, want %d\ngot: %v\nwant: %v", len(got), len(want), got, want) | ||
| } | ||
| for i, w := range want { | ||
| if got[i] != w { | ||
| t.Errorf("arg[%d] = %q, want %q", i, got[i], w) | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OS Command Injection (CWE-78)
More Details
OS command injection is a critical vulnerability that allows an attacker to execute arbitrary commands on the system. This can lead to a full system compromise, as the attacker can potentially gain complete control over the application and the underlying system.
The vulnerability arises when user input is used to construct commands or command arguments that are then executed by the application. This can occur when user-supplied data is passed directly to functions that execute OS commands, such as exec.Command, exec.CommandContext, syscall.ForkExec, or syscall.StartProcess.
If an attacker can inject malicious code into these commands, they can potentially execute any command on the system, including installing malware, stealing data, or causing a denial of service. This vulnerability can have severe consequences, including data breaches, system compromise, and unauthorized access to sensitive information.
To avoid this vulnerability, user input should never be used directly in constructing commands or command arguments. Instead, the application should use a hardcoded set of arguments and validate any user input to ensure it does not contain malicious code.
Remediation
To remediate this vulnerability, follow these recommendations:
Example of safely executing an OS command:
To ignore this finding as an exception, reply to this conversation with
#wiz_ignore reasonIf you'd like to ignore this finding in all future scans, add an exception in the .wiz file (learn more) or create an Ignore Rule (learn more).
To get more details on how to remediate this issue using AI, reply to this conversation with
#wiz remediate