# Root Cause Analysis: CVE-2026-59092

## Summary

JuiceFS through 1.3.1 exposes debug and metrics HTTP endpoints via the shared `http.DefaultServeMux`, enabling unauthenticated remote attackers to access sensitive `/debug/pprof/*` handlers. The `/debug/pprof/cmdline` endpoint leaks the full process command line, which includes metadata engine connection strings containing database credentials (e.g., Redis passwords), granting full read/write access to filesystem metadata. Other pprof handlers (`/debug/pprof/heap`, `/debug/pprof/goroutine`, `/debug/pprof/profile`) leak internal runtime state and can be abused for denial-of-service via CPU profiling.

## Impact

- **Package/component affected**: `cmd/mount.go` (`exposeMetrics` function), `pkg/fs/http.go` (`StartHTTPServer` for WebDAV), `pkg/sync/cluster.go` (`startManager`)
- **Affected versions**: JuiceFS through 1.3.1 (fixed in commit `a46979cdd4082217081ee99b931ddc53d038e47a`)
- **Risk level**: High (CVSS 7.7) — unauthenticated credential disclosure leading to metadata engine compromise and potential DoS

## Impact Parity

- **Disclosed/claimed maximum impact**: Authentication bypass; disclosure of metadata engine connection strings with DB credentials via `/debug/pprof/cmdline`; access to internal state via other pprof handlers; potential DoS via profiling endpoints
- **Reproduced impact from this run**: 
  - Unauthenticated access to `/debug/pprof/cmdline` returns HTTP 200 with full command line including Redis password `s3cr3tPass` from the metadata URL `redis://:s3cr3tPass@127.0.0.1:6379/1`
  - All pprof endpoints (`/debug/pprof/`, `/debug/pprof/heap`, `/debug/pprof/goroutine`, `/debug/pprof/profile`) return HTTP 200 without authentication
  - Fixed version returns HTTP 404 for all pprof endpoints while `/metrics` still works
- **Parity**: `full` — the authentication bypass and credential leakage are fully demonstrated through the real product (JuiceFS gateway) via the remote HTTP API surface
- **Not demonstrated**: Full metadata compromise (using the leaked credentials to access the Redis metadata engine) and DoS via profiling — these are downstream consequences of the credential leak, not separate reproduction targets

## Root Cause

The `exposeMetrics()` function in `cmd/mount.go` calls `http.Handle("/metrics", ...)` which registers the metrics handler on the shared `http.DefaultServeMux`. It then starts the HTTP server with `http.Serve(ln, nil)`, where the `nil` handler defaults to `http.DefaultServeMux`.

Since the `net/http/pprof` package is imported via `_ "net/http/pprof"` in multiple files (`cmd/main.go`, `cmd/mount.go`, `cmd/gateway.go`, `cmd/format.go`, `cmd/sync.go`), its `init()` function registers pprof handlers (`/debug/pprof/cmdline`, `/debug/pprof/heap`, `/debug/pprof/goroutine`, `/debug/pprof/profile`, etc.) on `http.DefaultServeMux` at program startup. As a result, all pprof endpoints are served without authentication alongside the metrics endpoint on whatever address the metrics server binds to (default `127.0.0.1:9567`, but configurable to `0.0.0.0:9567` for remote access).

The same pattern affects:
- `pkg/fs/http.go` `StartHTTPServer()` — WebDAV server uses `http.ListenAndServe(addr, nil)`
- `pkg/sync/cluster.go` `startManager()` — sync manager uses `http.Serve(l, nil)`

**Fix commit**: `a46979cdd4082217081ee99b931ddc53d038e47a` ("cmd: use a dedicated ServeMux to avoid exposing pprof/metrics")

The fix creates a dedicated `http.NewServeMux()` for each HTTP server and passes it to `http.Serve(ln, mux)` instead of `nil`, isolating the application handlers from the pprof handlers registered on `DefaultServeMux`.

### Vulnerable code (`cmd/mount.go`, commit `f60a90fc`):
```go
http.Handle("/metrics", promhttp.HandlerFor(...))  // registers on DefaultServeMux
// ...
http.Serve(ln, nil)  // nil → uses DefaultServeMux → exposes pprof
```

### Fixed code (`cmd/mount.go`, commit `a46979cd`):
```go
mux := http.NewServeMux()                           // dedicated mux
mux.Handle("/metrics", promhttp.HandlerFor(...))    // registers on dedicated mux
// ...
http.Serve(ln, mux)  // uses dedicated mux → no pprof exposure
```

## Reproduction Steps

1. **Reference**: `bundle/repro/reproduction_steps.sh`
2. **What the script does**:
   - Installs Go 1.25.0 and Redis if not already available
   - Clones the JuiceFS repository (or reuses the project cache)
   - Builds the vulnerable binary at commit `f60a90fc` (parent of the fix)
   - Builds the fixed binary at commit `a46979cd`
   - Starts Redis with password `s3cr3tPass` to simulate a credential-bearing metadata engine
   - Formats a JuiceFS volume using `redis://:s3cr3tPass@127.0.0.1:6379/1` as the metadata URL
   - Starts the JuiceFS S3 gateway with `--metrics 0.0.0.0:9567` (remotely accessible metrics port)
   - Sends unauthenticated HTTP GET to `/debug/pprof/cmdline` on the metrics port
   - Verifies the response contains the Redis password (vulnerable version)
   - Repeats with the fixed binary and verifies HTTP 404 (no pprof exposure)
3. **Expected evidence of reproduction**:
   - Vulnerable version: HTTP 200 for `/debug/pprof/cmdline` with response body containing `redis://:s3cr3tPass@127.0.0.1:6379/1`
   - Fixed version: HTTP 404 for `/debug/pprof/cmdline`
   - Both versions: HTTP 200 for `/metrics` (metrics endpoint still functional after fix)

## Evidence

### Log files
- `bundle/logs/gateway-vuln.log` — vulnerable gateway startup and metrics listening log
- `bundle/logs/gateway-fixed.log` — fixed gateway startup and metrics listening log
- `bundle/logs/redis.log` — Redis server log
- `bundle/logs/format.log` — JuiceFS volume format log

### Response artifacts
- `bundle/repro/artifacts/vuln-cmdline-response.txt` — raw response from vulnerable `/debug/pprof/cmdline`
- `bundle/repro/artifacts/fixed-cmdline-response.txt` — raw response from fixed `/debug/pprof/cmdline`
- `bundle/repro/artifacts/vuln-metrics-response.txt` — Prometheus metrics from vulnerable version

### Key excerpts

**Vulnerable `/debug/pprof/cmdline` response (HTTP 200):**
```
/data/pruva/project-cache/.../juicefs-vuln
gateway
redis://:s3cr3tPass@127.0.0.1:6379/1
localhost:9000
--metrics
0.0.0.0:9567
--no-banner
```

**Fixed `/debug/pprof/cmdline` response (HTTP 404):**
```
404 page not found
```

**Vulnerable pprof endpoints (all HTTP 200 without auth):**
```
/debug/pprof/: HTTP 200
/debug/pprof/heap: HTTP 200
/debug/pprof/goroutine: HTTP 200
/debug/pprof/profile: HTTP 200
/metrics: HTTP 200
```

**Fixed pprof endpoints (all HTTP 404):**
```
/debug/pprof/: HTTP 404
/debug/pprof/heap: HTTP 404
/debug/pprof/goroutine: HTTP 404
/debug/pprof/profile: HTTP 404
/metrics: HTTP 200
```

### Environment details
- Go version: go1.25.0 linux/amd64
- Redis version: 8.0.5
- JuiceFS vulnerable commit: `f60a90fc0ad52d2bb1f44f38a04d55044fc91d50`
- JuiceFS fixed commit: `a46979cdd4082217081ee99b931ddc53d038e47a`
- Architecture: x86_64

## Recommendations / Next Steps

1. **Upgrade**: Update to a JuiceFS version containing commit `a46979cd` or later
2. **Network restriction**: Bind the metrics port to localhost (`127.0.0.1:9567`) rather than `0.0.0.0:9567` unless remote monitoring is explicitly required
3. **Debug agent**: Use the `--no-agent` flag to disable the debug agent on port 6060, which still uses `http.ListenAndServe(debugAgent, nil)` with `DefaultServeMux` even after the fix (though it binds to localhost only)
4. **Firewall**: Restrict network access to the metrics and debug ports using firewall rules
5. **Credential rotation**: If credentials were exposed via this vulnerability, rotate all metadata engine passwords immediately

## Additional Notes

- **Idempotency**: The script was run twice consecutively, both times confirming the vulnerability with identical results (exit code 0)
- **Scope**: The fix addresses `exposeMetrics` (mount/gateway/sync/mdtest), `StartHTTPServer` (WebDAV), and `startManager` (sync cluster). The debug agent in `cmd/main.go` line 336 (`http.ListenAndServe(debugAgent, nil)`) remains unchanged but is bound to `127.0.0.1` only, limiting it to local access
- **Negative control**: The fixed version binary was built from the exact fix commit and demonstrates that pprof endpoints return 404 while `/metrics` continues to function, proving the fix is surgical and does not break metrics collection
