Skip to content

Commit f446c2d

Browse files
committed
fix(console,internal/ethapi,node,rpc): restrict debug_setHead to local transports
Add a local-only RPC API classification and use it to keep debug_setHead available over in-process RPC and IPC while hiding it from HTTP and WebSocket transports. This also updates the admin RPC startup path and adds regression coverage for console visibility, transport exposure, and local-only API leakage.
1 parent 146252a commit f446c2d

11 files changed

Lines changed: 441 additions & 56 deletions

File tree

cmd/XDC/consolecmd.go

Lines changed: 48 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package main
1818

1919
import (
2020
"fmt"
21+
"net/url"
2122
"os"
2223
"os/signal"
2324
"path/filepath"
@@ -85,10 +86,11 @@ func localConsole(ctx *cli.Context) error {
8586
utils.Fatalf("Failed to attach to the inproc XDC: %v", err)
8687
}
8788
config := console.Config{
88-
DataDir: utils.MakeDataDir(ctx),
89-
DocRoot: ctx.String(utils.JSpathFlag.Name),
90-
Client: client,
91-
Preload: utils.MakeConsolePreloads(ctx),
89+
DataDir: utils.MakeDataDir(ctx),
90+
DocRoot: ctx.String(utils.JSpathFlag.Name),
91+
Client: client,
92+
LocalTransport: true,
93+
Preload: utils.MakeConsolePreloads(ctx),
9294
}
9395

9496
console, err := console.New(config)
@@ -132,15 +134,16 @@ func remoteConsole(ctx *cli.Context) error {
132134
endpoint = fmt.Sprintf("%s/XDC.ipc", path)
133135
}
134136

135-
client, err := dialRPC(endpoint)
137+
client, localTransport, err := dialRPC(endpoint)
136138
if err != nil {
137139
utils.Fatalf("Unable to attach to remote XDC: %v", err)
138140
}
139141
config := console.Config{
140-
DataDir: utils.MakeDataDir(ctx),
141-
DocRoot: ctx.String(utils.JSpathFlag.Name),
142-
Client: client,
143-
Preload: utils.MakeConsolePreloads(ctx),
142+
DataDir: utils.MakeDataDir(ctx),
143+
DocRoot: ctx.String(utils.JSpathFlag.Name),
144+
Client: client,
145+
LocalTransport: localTransport,
146+
Preload: utils.MakeConsolePreloads(ctx),
144147
}
145148

146149
console, err := console.New(config)
@@ -164,15 +167,39 @@ func remoteConsole(ctx *cli.Context) error {
164167
// dialRPC returns a RPC client which connects to the given endpoint.
165168
// The check for empty endpoint implements the defaulting logic
166169
// for "XDC attach" and "XDC monitor" with no argument.
167-
func dialRPC(endpoint string) (*rpc.Client, error) {
170+
func dialRPC(endpoint string) (*rpc.Client, bool, error) {
171+
endpoint, localTransport := resolveConsoleEndpoint(endpoint)
172+
client, err := rpc.Dial(endpoint)
173+
return client, localTransport, err
174+
}
175+
176+
func resolveConsoleEndpoint(endpoint string) (string, bool) {
168177
if endpoint == "" {
169-
endpoint = node.DefaultIPCEndpoint(clientIdentifier)
170-
} else if strings.HasPrefix(endpoint, "rpc:") || strings.HasPrefix(endpoint, "ipc:") {
171-
// Backwards compatibility with geth < 1.5 which required
172-
// these prefixes.
173-
endpoint = endpoint[4:]
178+
return node.DefaultIPCEndpoint(clientIdentifier), true
179+
}
180+
if strings.HasPrefix(endpoint, "ipc:") {
181+
// Backwards compatibility with geth < 1.5 which required these prefixes.
182+
return endpoint[4:], true
183+
}
184+
// Backwards compatibility with geth < 1.5 which required this prefix.
185+
// Strip the legacy prefix, then classify the resulting endpoint based
186+
// on its actual transport instead of assuming it is local.
187+
endpoint = strings.TrimPrefix(endpoint, "rpc:")
188+
if endpoint == "stdio" {
189+
return endpoint, false
190+
}
191+
u, err := url.Parse(endpoint)
192+
if err != nil {
193+
return endpoint, false
194+
}
195+
switch u.Scheme {
196+
case "http", "https", "ws", "wss", "stdio":
197+
return endpoint, false
198+
case "":
199+
return endpoint, true
200+
default:
201+
return endpoint, false
174202
}
175-
return rpc.Dial(endpoint)
176203
}
177204

178205
// ephemeralConsole starts a new XDC node, attaches an ephemeral JavaScript
@@ -190,10 +217,11 @@ func ephemeralConsole(ctx *cli.Context) error {
190217
utils.Fatalf("Failed to attach to the inproc XDC: %v", err)
191218
}
192219
config := console.Config{
193-
DataDir: utils.MakeDataDir(ctx),
194-
DocRoot: ctx.String(utils.JSpathFlag.Name),
195-
Client: client,
196-
Preload: utils.MakeConsolePreloads(ctx),
220+
DataDir: utils.MakeDataDir(ctx),
221+
DocRoot: ctx.String(utils.JSpathFlag.Name),
222+
Client: client,
223+
LocalTransport: true,
224+
Preload: utils.MakeConsolePreloads(ctx),
197225
}
198226

199227
console, err := console.New(config)

cmd/XDC/consolecmd_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,44 @@ at block: 0 ({{niltime}}){{if ipc}}
160160
attach.ExpectExit()
161161
}
162162

163+
func TestResolveConsoleEndpoint(t *testing.T) {
164+
tests := []struct {
165+
name string
166+
endpoint string
167+
wantLocal bool
168+
wantPrefix string
169+
}{
170+
{name: "default ipc endpoint", endpoint: "", wantLocal: true, wantPrefix: ""},
171+
{name: "explicit ipc path", endpoint: "/tmp/XDC.ipc", wantLocal: true, wantPrefix: "/tmp/XDC.ipc"},
172+
{name: "legacy ipc prefix", endpoint: "ipc:/tmp/XDC.ipc", wantLocal: true, wantPrefix: "/tmp/XDC.ipc"},
173+
{name: "legacy rpc prefix", endpoint: "rpc:/tmp/XDC.ipc", wantLocal: true, wantPrefix: "/tmp/XDC.ipc"},
174+
{name: "legacy rpc http prefix", endpoint: "rpc:http://localhost:8545", wantPrefix: "http://localhost:8545", wantLocal: false},
175+
{name: "legacy rpc ws prefix", endpoint: "rpc:ws://localhost:8546", wantPrefix: "ws://localhost:8546", wantLocal: false},
176+
{name: "stdio endpoint", endpoint: "stdio", wantLocal: false, wantPrefix: "stdio"},
177+
{name: "legacy rpc stdio prefix", endpoint: "rpc:stdio", wantLocal: false, wantPrefix: "stdio"},
178+
{name: "http endpoint", endpoint: "http://localhost:8545", wantLocal: false, wantPrefix: "http://localhost:8545"},
179+
{name: "ws endpoint", endpoint: "ws://localhost:8546", wantLocal: false, wantPrefix: "ws://localhost:8546"},
180+
}
181+
182+
for _, test := range tests {
183+
t.Run(test.name, func(t *testing.T) {
184+
gotEndpoint, gotLocal := resolveConsoleEndpoint(test.endpoint)
185+
if gotLocal != test.wantLocal {
186+
t.Fatalf("unexpected local transport classification: got %v want %v", gotLocal, test.wantLocal)
187+
}
188+
if test.wantPrefix == "" {
189+
if !strings.HasSuffix(gotEndpoint, "XDC.ipc") {
190+
t.Fatalf("expected default IPC endpoint, got %q", gotEndpoint)
191+
}
192+
return
193+
}
194+
if gotEndpoint != test.wantPrefix {
195+
t.Fatalf("unexpected resolved endpoint: got %q want %q", gotEndpoint, test.wantPrefix)
196+
}
197+
})
198+
}
199+
}
200+
163201
// trulyRandInt generates a crypto random integer used by the console tests to
164202
// not clash network ports with other tests running cocurrently.
165203
func trulyRandInt(lo, hi int) int {

console/console.go

Lines changed: 54 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -51,26 +51,28 @@ const DefaultPrompt = "> "
5151
// Config is the collection of configurations to fine tune the behavior of the
5252
// JavaScript console.
5353
type Config struct {
54-
DataDir string // Data directory to store the console history at
55-
DocRoot string // Filesystem path from where to load JavaScript files from
56-
Client *rpc.Client // RPC client to execute Ethereum requests through
57-
Prompt string // Input prompt prefix string (defaults to DefaultPrompt)
58-
Prompter UserPrompter // Input prompter to allow interactive user feedback (defaults to TerminalPrompter)
59-
Printer io.Writer // Output writer to serialize any display strings to (defaults to os.Stdout)
60-
Preload []string // Absolute paths to JavaScript files to preload
54+
DataDir string // Data directory to store the console history at
55+
DocRoot string // Filesystem path from where to load JavaScript files from
56+
Client *rpc.Client // RPC client to execute Ethereum requests through
57+
LocalTransport bool // Whether the console is attached over an in-process or IPC transport
58+
Prompt string // Input prompt prefix string (defaults to DefaultPrompt)
59+
Prompter UserPrompter // Input prompter to allow interactive user feedback (defaults to TerminalPrompter)
60+
Printer io.Writer // Output writer to serialize any display strings to (defaults to os.Stdout)
61+
Preload []string // Absolute paths to JavaScript files to preload
6162
}
6263

6364
// Console is a JavaScript interpreted runtime environment. It is a fully fleged
6465
// JavaScript console attached to a running node via an external or in-process RPC
6566
// client.
6667
type Console struct {
67-
client *rpc.Client // RPC client to execute Ethereum requests through
68-
jsre *jsre.JSRE // JavaScript runtime environment running the interpreter
69-
prompt string // Input prompt prefix string
70-
prompter UserPrompter // Input prompter to allow interactive user feedback
71-
histPath string // Absolute path to the console scrollback history
72-
history []string // Scroll history maintained by the console
73-
printer io.Writer // Output writer to serialize any display strings to
68+
client *rpc.Client // RPC client to execute Ethereum requests through
69+
jsre *jsre.JSRE // JavaScript runtime environment running the interpreter
70+
localTransport bool // Whether the connected transport is in-process or IPC
71+
prompt string // Input prompt prefix string
72+
prompter UserPrompter // Input prompter to allow interactive user feedback
73+
histPath string // Absolute path to the console scrollback history
74+
history []string // Scroll history maintained by the console
75+
printer io.Writer // Output writer to serialize any display strings to
7476
}
7577

7678
// New initializes a JavaScript interpreted runtime environment and sets defaults
@@ -89,12 +91,13 @@ func New(config Config) (*Console, error) {
8991

9092
// Initialize the console and return
9193
console := &Console{
92-
client: config.Client,
93-
jsre: jsre.New(config.DocRoot, config.Printer),
94-
prompt: config.Prompt,
95-
prompter: config.Prompter,
96-
printer: config.Printer,
97-
histPath: filepath.Join(config.DataDir, HistoryFile),
94+
client: config.Client,
95+
jsre: jsre.New(config.DocRoot, config.Printer),
96+
localTransport: config.LocalTransport,
97+
prompt: config.Prompt,
98+
prompter: config.Prompter,
99+
printer: config.Printer,
100+
histPath: filepath.Join(config.DataDir, HistoryFile),
98101
}
99102
if err := os.MkdirAll(config.DataDir, 0700); err != nil {
100103
return nil, err
@@ -207,9 +210,25 @@ func (c *Console) initExtensions() error {
207210
}
208211
}
209212
})
213+
if !c.localTransport {
214+
c.hideUnavailableDebugMethods()
215+
}
210216
return nil
211217
}
212218

219+
func (c *Console) hideUnavailableDebugMethods() {
220+
c.jsre.Do(func(vm *goja.Runtime) {
221+
if debug := getObject(vm, "debug"); debug != nil {
222+
debug.Set("setHead", goja.Undefined())
223+
}
224+
if web3 := getObject(vm, "web3"); web3 != nil {
225+
if debug := web3.Get("debug"); debug != nil && !goja.IsUndefined(debug) && !goja.IsNull(debug) {
226+
debug.ToObject(vm).Set("setHead", goja.Undefined())
227+
}
228+
}
229+
})
230+
}
231+
213232
// initAdmin creates additional admin APIs implemented by the bridge.
214233
func (c *Console) initAdmin(vm *goja.Runtime, bridge *bridge) {
215234
if admin := getObject(vm, "admin"); admin != nil {
@@ -260,7 +279,21 @@ func (c *Console) AutoCompleteInput(line string, pos int) (string, []string, str
260279
start++
261280
break
262281
}
263-
return line[:start], c.jsre.CompleteKeywords(line[start:pos]), line[pos:]
282+
return line[:start], c.filterCompletions(c.jsre.CompleteKeywords(line[start:pos])), line[pos:]
283+
}
284+
285+
func (c *Console) filterCompletions(completions []string) []string {
286+
if c.localTransport {
287+
return completions
288+
}
289+
filtered := completions[:0]
290+
for _, completion := range completions {
291+
if completion == "debug.setHead" || completion == "debug.setHead(" || completion == "web3.debug.setHead" || completion == "web3.debug.setHead(" {
292+
continue
293+
}
294+
filtered = append(filtered, completion)
295+
}
296+
return filtered
264297
}
265298

266299
// Welcome show summary of current Geth instance and some metadata about the

0 commit comments

Comments
 (0)