Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
1ea12e2
fix: copy config strings to request pool to prevent segfaults
RyanKung Dec 4, 2025
4c8be25
fix: add panic protection for config memory access to prevent segfaults
RyanKung Dec 4, 2025
b713013
fix: add repr(C) to X402Config for C-compatible memory layout
RyanKung Dec 4, 2025
9415de6
refactor: use ngx-rust safe APIs instead of raw pointers
RyanKung Dec 4, 2025
f5a2fd7
refactor: use safe Request API instead of raw pointers in phase handler
RyanKung Dec 4, 2025
73eb617
fix: make req_mut mutable for clear_x402_content_handler calls
RyanKung Dec 4, 2025
1feedcd
fix: remove unused imports after refactoring
RyanKung Dec 4, 2025
7043afb
fix: remove needless borrows in phase handler
RyanKung Dec 4, 2025
cf14c6e
fix: remove all needless borrows in phase handler
RyanKung Dec 4, 2025
58c99bb
fix: fix remaining needless borrows in log_debug calls
RyanKung Dec 4, 2025
7a1fff3
fix: use immutable references in log_debug calls
RyanKung Dec 4, 2025
adabaf0
fix: remove needless borrows in function calls
RyanKung Dec 4, 2025
7e121ef
fix: use direct references instead of deref-and-borrow pattern
RyanKung Dec 4, 2025
4399bb0
fix: remove needless borrows in phase handler (final fix)
RyanKung Dec 4, 2025
7158895
fix: remove all needless borrows - use req_mut directly where possible
RyanKung Dec 4, 2025
be2b4c7
fix: restore necessary borrows for function calls that require refere…
RyanKung Dec 4, 2025
dadfda1
security: add buffer bounds checking and pointer validation
RyanKung Dec 4, 2025
87b77ae
fix: remove unnecessary unsafe block - from_raw_parts is already in u…
RyanKung Dec 4, 2025
ddf9c07
refactor: replace unsafe code with safer APIs
RyanKung Dec 4, 2025
cb0a16b
security: add panic protection to all unsafe pointer operations
RyanKung Dec 4, 2025
d7ffb83
fix: add panic protection for read_volatile in get_module_config
RyanKung Dec 4, 2025
f1e4aeb
fix: remove unnecessary unsafe block - read_volatile already in unsaf…
RyanKung Dec 4, 2025
54c8463
style: fix clippy warnings
RyanKung Dec 4, 2025
4e8eff4
style: fix remaining clippy warnings and syntax errors
RyanKung Dec 4, 2025
600d445
style: fix all remaining clippy warnings
RyanKung Dec 4, 2025
2e3af49
style: fix final clippy warnings - remove needless borrows
RyanKung Dec 4, 2025
0a76352
fix: restore mut for req_mut and fix clear_x402_content_handler calls
RyanKung Dec 4, 2025
cd6c949
fix: resolve all compilation errors and clippy warnings
RyanKung Dec 4, 2025
cb24d62
fix: add mut to req_mut and fix clear_x402_content_handler call
RyanKung Dec 4, 2025
935bf02
style: fix final clippy warnings - remove needless borrows
RyanKung Dec 4, 2025
17f6d2c
style: remove all needless borrows to fix clippy warnings
RyanKung Dec 4, 2025
a0684e5
fix: restore &mut req_mut for clear_x402_content_handler calls
RyanKung Dec 4, 2025
a3780ce
style: add allow attributes for necessary borrows
RyanKung Dec 4, 2025
b8d87a8
style: add missing allow attribute for first clear_x402_content_handl…
RyanKung Dec 4, 2025
178ea3e
style: add allow attribute for question-mark warning
RyanKung Dec 4, 2025
523f94f
fix: remove unnecessary &mut borrows in clear_x402_content_handler calls
RyanKung Dec 4, 2025
c1062a9
fix: remove all unnecessary &mut borrows in clear_x402_content_handle…
RyanKung Dec 4, 2025
abff3fb
fix: remove mut from req_mut - Request implements DerefMut
RyanKung Dec 4, 2025
a91a4ec
fix: restore mut for req_mut and remove unnecessary &mut borrows
RyanKung Dec 4, 2025
b1314bd
fix: remove unnecessary mut from req_mut
RyanKung Dec 4, 2025
e69ee1f
fix: replace match with ? operator to fix clippy warning
RyanKung Dec 4, 2025
cac448e
fix: move get_recent_docker_logs to common module and fix imports
RyanKung Dec 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/ngx_module/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ use std::str::FromStr;
use std::time::Duration;

/// Module configuration (raw strings from Nginx config)
///
/// # Memory Layout
///
/// This struct must use C-compatible memory layout (`#[repr(C)]`) because:
/// 1. It's allocated by nginx using `ngx_pcalloc` (C memory allocator)
/// 2. It's accessed via raw pointers from nginx's configuration system
/// 3. Field order and padding must match what nginx expects
/// 4. Adding new fields (like `ttl_str`) must not break existing memory layout
#[repr(C)]
#[derive(Clone, Default)]
pub struct X402Config {
pub enabled: ngx::ffi::ngx_flag_t,
Expand Down
137 changes: 64 additions & 73 deletions src/ngx_module/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,18 +101,21 @@ pub unsafe extern "C" fn x402_metrics_handler(
/// # Safety
///
/// The caller must ensure that `r` is a valid pointer to a `ngx_http_request_t`.
unsafe fn clear_x402_content_handler(r: *mut ngx::ffi::ngx_http_request_t, reason: &str) {
use ngx::ffi::ngx_http_request_t;
let r_raw = r.cast::<ngx_http_request_t>();
if r_raw.is_null() {
return;
}
/// Clear x402 content handler if it's set
///
/// Uses ngx-rust's safe `Request` API instead of raw pointers.
fn clear_x402_content_handler(req: &mut ngx::http::Request, reason: &str) {
use crate::ngx_module::logging::log_debug;

extern "C" {
fn x402_ngx_handler(r: *mut ngx::ffi::ngx_http_request_t) -> ngx::ffi::ngx_int_t;
}

// Use Request's as_mut() to access the underlying structure
// Safe: Request is a zero-cost wrapper, as_mut() returns a valid mutable reference
let r_raw = req.as_mut();
let x402_handler_fn: ngx::ffi::ngx_http_handler_pt = Some(x402_ngx_handler);
let current_handler = (*r_raw).content_handler;
let current_handler = r_raw.content_handler;

// Check if content handler is x402_ngx_handler
let is_x402_handler = if let (Some(current), Some(x402)) = (current_handler, x402_handler_fn) {
Expand All @@ -123,8 +126,7 @@ unsafe fn clear_x402_content_handler(r: *mut ngx::ffi::ngx_http_request_t, reaso

if is_x402_handler {
// Clear content handler to prevent payment verification in CONTENT_PHASE
(*r_raw).content_handler = None;
use crate::ngx_module::logging::log_debug;
r_raw.content_handler = None;
log_debug(
None,
&format!(
Expand Down Expand Up @@ -167,13 +169,12 @@ pub unsafe extern "C" fn x402_phase_handler(
|| {
// Create Request object safely using ngx-rust's API
// Request is a zero-cost wrapper (repr(transparent)), so we can safely create it from the raw pointer
// Request implements DerefMut, so we can pass req_mut directly to functions expecting &mut Request
let req_mut = ngx::http::Request::from_ngx_http_request(r);

use crate::ngx_module::logging::log_debug;
use crate::ngx_module::module::get_module_config;
use crate::ngx_module::request::{
get_http_method, is_websocket_request, should_skip_payment_for_method,
};
use crate::ngx_module::request::is_websocket_request;

// Skip payment verification for special request types
// These requests should bypass payment verification:
Expand All @@ -190,9 +191,9 @@ pub unsafe extern "C" fn x402_phase_handler(

// Check if HTTP method should skip payment verification
// This check happens early to avoid unnecessary processing
let request_struct = unsafe { &*r };
let method_id = request_struct.method;
let detected_method = unsafe { get_http_method(r) };
// Use safe Request API instead of raw pointer access
let method_id = crate::ngx_module::request::get_http_method_id(req_mut);
let detected_method = crate::ngx_module::request::get_http_method(req_mut);

log_debug(
Some(req_mut),
Expand All @@ -202,7 +203,7 @@ pub unsafe extern "C" fn x402_phase_handler(
),
);

if unsafe { should_skip_payment_for_method(r) } {
if crate::ngx_module::request::should_skip_payment_for_method(req_mut) {
let method = detected_method.unwrap_or("UNKNOWN");
log_debug(
Some(req_mut),
Expand All @@ -214,12 +215,10 @@ pub unsafe extern "C" fn x402_phase_handler(
// Clear content handler if it's x402_ngx_handler to prevent payment verification in CONTENT_PHASE
// When returning NGX_DECLINED, nginx will still proceed to CONTENT_PHASE, so we need to clear
// the content handler to prevent duplicate payment verification
unsafe {
clear_x402_content_handler(
r,
&format!("for {} request to prevent payment verification", method),
);
}
clear_x402_content_handler(
req_mut,
&format!("for {} request to prevent payment verification", method),
);
return ngx::ffi::NGX_DECLINED as ngx::ffi::ngx_int_t;
}

Expand All @@ -230,63 +229,57 @@ pub unsafe extern "C" fn x402_phase_handler(
"[x402] Phase handler: WebSocket upgrade detected, skipping payment verification",
);
// Clear content handler if it's x402_ngx_handler to prevent payment verification in CONTENT_PHASE
unsafe {
clear_x402_content_handler(
r,
"for WebSocket request to prevent payment verification",
);
}
clear_x402_content_handler(
req_mut,
"for WebSocket request to prevent payment verification",
);
return ngx::ffi::NGX_DECLINED as ngx::ffi::ngx_int_t;
}

// Check for subrequest using raw request pointer
// Subrequests have r->parent != NULL
unsafe {
let r_raw = r.cast_const();
if !r_raw.is_null() {
let parent = (*r_raw).parent;
if !parent.is_null() {
log_debug(
Some(req_mut),
"[x402] Phase handler: Subrequest detected (parent != NULL), skipping payment verification",
);
return ngx::ffi::NGX_DECLINED as ngx::ffi::ngx_int_t;
// Use panic protection to catch any invalid memory access
use crate::ngx_module::panic_handler::catch_panic;
let is_subrequest = catch_panic(
|| {
unsafe {
let r_raw = r.cast_const();
if r_raw.is_null() {
return false;
}
// Access parent field - this may cause segfault if memory is invalid
let parent = (*r_raw).parent;
!parent.is_null()
}
}
},
"check subrequest (parent field)",
)
.unwrap_or(false);

if is_subrequest {
log_debug(
Some(req_mut),
"[x402] Phase handler: Subrequest detected (parent != NULL), skipping payment verification",
);
return ngx::ffi::NGX_DECLINED as ngx::ffi::ngx_int_t;
}

// Check for internal redirect using raw request pointer
// Internal redirects have r->internal = 1 (unsigned flag)
// In nginx C code, internal is a field in ngx_http_request_t structure
unsafe {
let r_raw = r.cast_const();
if !r_raw.is_null() {
// Access internal field directly from C structure
// internal is an unsigned integer field in ngx_http_request_t
// We need to access it via pointer dereference
// Note: This is accessing the raw C structure, so we need to be careful
let request_struct = &*r_raw;
// Try to access internal field - it should be a field, not a method
// If this doesn't compile, we may need to use offset_of! macro or other approach
// For now, we'll use a workaround: check if uri.data starts with @ (named locations)
// Named locations (like @fallback) are always internal redirects
let uri = request_struct.uri;
if !uri.data.is_null() && uri.len > 0 {
// Check if URI starts with '@' which indicates named location (always internal)
let uri_slice =
std::slice::from_raw_parts(uri.data.cast_const(), uri.len.min(1));
if uri_slice[0] == b'@' {
log_debug(
Some(req_mut),
"[x402] Phase handler: Internal redirect detected (named location @), skipping payment verification",
);
return ngx::ffi::NGX_DECLINED as ngx::ffi::ngx_int_t;
}
}
// Check for internal redirect using safe Request API
// Internal redirects in nginx are typically named locations (starting with @)
// We can detect this by checking the request path using Request::path()
// This is safer than accessing raw C structure fields
if let Ok(path) = req_mut.path().to_str() {
if path.starts_with('@') {
log_debug(
Some(req_mut),
"[x402] Phase handler: Internal redirect detected (named location @), skipping payment verification",
);
return ngx::ffi::NGX_DECLINED as ngx::ffi::ngx_int_t;
}
}

// Check if module is enabled for this location
#[allow(clippy::question_mark)] // Need to return NGX_DECLINED on error, can't use ?
let conf = match get_module_config(req_mut) {
Ok(c) => c,
Err(_) => {
Expand All @@ -310,12 +303,10 @@ pub unsafe extern "C" fn x402_phase_handler(
// If content handler is x402_ngx_handler (no proxy_pass), clear it to prevent
// duplicate payment verification in CONTENT_PHASE. If content handler is something
// else (like proxy_pass), keep it so it runs in CONTENT_PHASE.
unsafe {
clear_x402_content_handler(
r,
"after payment verification to prevent duplicate verification",
);
}
clear_x402_content_handler(
req_mut,
"after payment verification to prevent duplicate verification",
);
// This will proceed to CONTENT_PHASE where proxy_pass handler will run (if set)
ngx::ffi::NGX_OK as ngx::ffi::ngx_int_t
}
Expand Down
Loading