Skip to content

GET /v2/positionInfo/{marketIndex}` triggers SIGSEGV (null pointer dereference) — gateway process killed #153

Description

@jgustave

GET /v2/positionInfo/{marketIndex} triggers SIGSEGV (null pointer dereference) — gateway process killed

Summary

Calling GET /v2/positionInfo/{marketIndex} when an open perp position exists causes a segfault in libdrift_ffi_sys.so, killing the gateway process. The endpoint works correctly (returns HTTP 400 "no position") when no position exists, but crashes when it attempts to compute derived fields for an active position.

Environment

  • Gateway version: v1.5.5 (commit 056c890)
  • libdrift_ffi_sys: v2.158.0 (sha256: 3725759a1dab275f087cac6afc694ce0ad94a6295bf40643664ad28ac7ef705d)
  • Platform: Amazon Linux 2023, x86_64 (6.1.163-186.299.amzn2023.x86_64)
  • Docker: 25.0.14
  • RPC: Helius mainnet-beta (paid tier)
  • Mode: Delegate mode (--delegate <DELEGATOR_PUBKEY>)
  • Markets: --markets btc-perp

Steps to reproduce

  1. Start the gateway in delegate mode with --markets btc-perp
  2. Place a market order that fills (creates a perp position):
    curl -X POST http://localhost:8080/v2/orders \
      -H "Content-Type: application/json" \
      -d '{"orders": [{"marketIndex": 1, "marketType": "perp", "amount": "0.0001", "orderType": "market", "userOrderId": 1}]}'
  3. Wait for the order to fill (~10–30s). Verify position exists:
    curl http://localhost:8080/v2/positions
    # Should show: {"perp": [{"amount": "0.0001", "averageEntry": "...", "marketIndex": 1}]}
  4. Call positionInfo:
    curl http://localhost:8080/v2/positionInfo/1
  5. Gateway process is killed. curl receives an empty reply or connection refused.

Expected behavior

Returns JSON with extended position info (amount, averageEntry, liquidationPrice, unrealizedPnL, oraclePrice).

Actual behavior

The gateway process is killed by SIGSEGV. No error response is returned — the TCP connection drops. No panic or backtrace appears in gateway logs (even with RUST_BACKTRACE=1), because the crash occurs in native FFI code.

dmesg output

[13027.401426] traps: drift-gateway[14029] general protection fault ip:7f11cfe6250f sp:7ffe807c3b40 error:0 in libc.so.6[7f11cfe62000+156000]
[15446.066666] actix-rt|system[14300]: segfault at 0 ip 0000000000000000 sp 00007fefe614bbe0 error 14 in drift-gateway[561061274000+10e000] likely on CPU 1 (core 0, socket 0)
[15465.984002] actix-rt|system[15879]: segfault at 0 ip 0000000000000000 sp 00007fd1cd251be0 error 14 in drift-gateway[56002c285000+10e000] likely on CPU 1 (core 0, socket 0)
[75414.389951] actix-rt|system[44050]: segfault at 0 ip 0000000000000000 sp 00007fcb99ff2be0 error 14 in drift-gateway[557012d83000+10e000] likely on CPU 1 (core 0, socket 0)
[82546.455786] actix-rt|system[50510]: segfault at 0 ip 0000000000000000 sp 00007f66631f1be0 error 14 in drift-gateway[55555ed0b000+10e000] likely on CPU 1 (core 0, socket 0)

The ip 0000000000000000 indicates a null function pointer call. The crash originates in the actix-rt worker thread handling the HTTP request.

Root cause analysis

We traced the crash through the full call chain:

gateway: get_position_extended()              [controller.rs]
  → calculate_liquidation_price_and_unrealized_pnl()  [drift-rs: math/liquidation.rs]
    → calculate_liquidation_price_inner()             [drift-rs: math/liquidation.rs]
      → calculate_margin_requirement_and_total_collateral_and_liability_info()  [FFI into libdrift_ffi_sys.so]
        → SIGSEGV at ip 0x0000000000000000            [inside prebuilt .so]

The crash is inside libdrift_ffi_sys.so, not in the Rust code. Specifically, calculate_margin_requirement_and_total_collateral_and_liability_info (the FFI function exported from drift-ffi-sys/src/exports.rs) dereferences a null function pointer during margin computation.

We verified this by patching drift-rs — replacing the three .expect() calls in math/liquidation.rs with proper .ok_or_else() error propagation (jgustave/drift-rs@f0571f0). The gateway was rebuilt with this fix. The SIGSEGV persisted — confirming the crash is in the prebuilt FFI binary, not in the Rust wrapper's error handling.

There is also a secondary bug in drift-rs: the calculate_liquidation_price_and_unrealized_pnl function uses .expect("market exists") and .expect("oracle loaded") which can panic (and propagate undefined behavior across the FFI boundary) when market configs or oracles are not yet loaded. This is a separate issue from the SIGSEGV but should also be fixed. PR available: jgustave/drift-rs@f0571f0.

Related issues

Workaround

Avoid calling GET /v2/positionInfo/{marketIndex}. Use GET /v2/positions instead, which returns amount, averageEntry, and marketIndex without invoking the FFI computation. Compute unrealized PnL client-side from the average entry price and current oracle price. liquidationPrice is unavailable until this bug is fixed.

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