Skip to content

Management REST API binds all interfaces with no authentication; app load path runs unverified bytes #96

Description

Summary

The management REST API defaults to 0.0.0.0:3001 with no authentication on any route, and POST /app feeds caller-supplied bytes straight into a dlopen inside the controller process. For the documented single-host usage (jrtc-ctl against localhost:3001) nothing is exposed; but the defaults an integrator inherits before wiring a management framework are bind-all-interfaces with anonymous app loading. Two asks: safest-by-default binding plus an optional auth token — and one small memory-safety fix found next to it.

1. Unauthenticated management API on all interfaces, feeding an unsigned native-code load path

In src/rest_api_lib/src/lib.rs the router has no authentication or authorization middleware on any route, and the server binds every interface:

let app = Router::new()
    .route("/app", get(get_apps).post(load_app))
    .route("/app/:id", get(get_app).delete(unload_app))
    .with_state(state);

let addr = SocketAddr::from(([0, 0, 0, 0], port));

load_app takes the base64 app bytes from POST /app and passes them to the C load_app callback (src/controller/jrtc_int.c L300-335), which writes them to a memfd and calls dlopen("/proc/self/fd/N", RTLD_LAZY) (_jrtc_load_app_from_memory, src/controller/jrtc_int.c L149-176). There is no signature, hash, or allowlist check anywhere on this path.

Consequence: any host that can reach port 3001 can load arbitrary native code into the controller process — the process holding the jbpf shared-memory telemetry and control channels to the instrumented network functions. On a testbed where the controller and the RAN workloads share a bridge network, that is every peer on the bridge. The app_type: "python" route reaches a similar outcome: REST-supplied paths are appended to sys.path and REST-supplied modules are imported (src/pythonapp_loader/jrtc_pythonapp_loader.c L161-169, L357-367).

The README positions the management plane as integrator territory — jrtc-ctl "can be optionally integrated with other management frameworks (e.g. nRT-RIC or SMO in O-RAN terminology)". That is exactly why the built-in defaults should be safe without such a framework: a deployment that has not integrated one inherits today's bind-everywhere, no-auth defaults, and startup prints listening on 0.0.0.0:3001 with no indication that the port loads native code.

Minimal check (observed from source; runs in seconds): start the controller on host A, then from host B on the same network — no credential required, the shared library is loaded into the controller process:

curl -s -X POST http://A:3001/app -H 'content-type: application/json' \
  -d '{"app":"<base64 of a .so exporting the jrtc app entry points>","app_name":"probe","runtime_us":1000,"deadline_us":0,"period_us":1000,"ioq_size":16,"app_path":"/tmp/probe","app_type":"binary","app_params":{},"device_mapping":{},"app_modules":[]}'

Expected: loopback by default, and remote management possible only after the integrator turns it on deliberately.

Suggested fixes:

  • Default the REST bind to 127.0.0.1, with a configurable bind address for integrators who want remote management.
  • Add optional shared-token authentication (e.g. a bearer token from an environment variable or the config file) enforced as axum middleware, so a remote bind never means anonymous app loading.
  • Optionally verify app payloads against an integrator-configured hash allowlist before dlopen, so only artifacts the integrator has blessed can be loaded.

2. unload_app indexes app_envs[app_id] with no bounds check

DELETE /app/{id} passes the raw i32 path segment to the C unload_app (src/controller/jrtc_int.c L396-400), which indexes the fixed-size global array struct jrtc_app_env* app_envs[MAX_NUM_JRTC_APPS] (declared at L95) with no range check — a negative or large id reads out of bounds. A one-line guard closes it:

if (app_id < 0 || app_id >= MAX_NUM_JRTC_APPS) return -1;

and the REST layer could map out-of-range ids to a 400 response.

Environment

jrt-controller @ f18345e0fb23eb5bebdfead10b1335678c9dcaf2 (main, 2026-09-15). Findings from source review; the curl one-liner above is the minimal runtime check.

Happy to send PRs for any of these if the approach sounds right.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions