influxdata/influxdb · error · anyhow::Error

call site contains null bytes

Error message

call site contains null bytes

What it means

The call-site expression (the snippet evaluated by py.eval to fetch the plugin function) is also converted to CString, which fails on interior NUL bytes; the error is wrapped with 'call site contains null bytes'. Like the code variant, this guards against NUL-corrupted input reaching the Python interpreter.

Solutions

  1. Sanitize the inputs used to build the call site (module name, function name): reject or strip '\x00'.
  2. Validate plugin metadata files are clean UTF-8 text before loading.
  3. Check where call_site is constructed and add an assertion/log there to catch the corruption source.

Example fix

# before
call_site = f"{module}.{fn}()"
# after
call_site = f"{module}.{fn}()"
if '\x00' in call_site:
    raise ValueError('call site contains NUL bytes')
Defensive patterns

Strategy: validation

Validate before calling

if '\x00' in call_site:
    raise ValueError('call site contains NUL bytes')  # reject before load

Try / catch

# treat like the code variant: catch the error with context 'call site contains null bytes' and reject the plugin metadata

Prevention

When it happens

Trigger: load_plugin_function (via execute_wal_flush_trigger / execute_schedule_trigger / execute_request_trigger) invoked with a call_site string containing '\x00', typically when it is built from user- or config-supplied parts such as module or function names.

Common situations: Function/module names loaded from binary config or environment data; corrupted plugin metadata; path components containing encoded NULs.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of influxdata/influxdb@06200ef96b (2026-09-19). Data as JSON: /api/errors/8604eecee8386cfb. Report an issue: GitHub.

Appendix: source

Thrown at influxdb3_py_api/src/system_py.rs:294

/// retrieves the function.
fn load_plugin_function<'py>(
    py: Python<'py>,
    code: &str,
    plugin_root: Option<&Path>,
    call_site: &str,
    missing_fn_error: ExecutePluginError,
) -> Result<Bound<'py, PyAny>, ExecutePluginError> {
    if let Some(root_path) = plugin_root {
        load_function_from_module(py, root_path, call_site, missing_fn_error)
    } else {
        // Create isolated globals for this plugin execution. Without this, single-file
        // plugins would share __main__'s namespace and could overwrite each other's
        // function definitions during concurrent execution.
        let globals = PyDict::new(py);
        let code = CString::new(code)
            .map_err(|e| anyhow::Error::new(e).context("plugin code contains null bytes"))?;
        let call_site = CString::new(call_site)
            .map_err(|e| anyhow::Error::new(e).context("call site contains null bytes"))?;
        py.run(&code, Some(&globals), None)
            .map_err(anyhow::Error::from)?;
        py.eval(&call_site, Some(&globals), None)
            .map_err(|_| missing_fn_error)
    }
}

/// Serializes temporary `sys.path` mutation during multi-file plugin imports.
static SYS_PATH_LOCK: LazyLock<Mutex<()>> = LazyLock::new(|| Mutex::new(()));

/// True when `err` is a `ModuleNotFoundError` for `module_name` itself, so no
/// plugin code has run and re-importing is side-effect free.
fn is_module_not_found(py: Python<'_>, err: &PyErr, module_name: &str) -> bool {
    err.is_instance_of::<PyModuleNotFoundError>(py)
        && err
            .value(py)
            .getattr("name")
            .ok()

View on GitHub (pinned to 06200ef96b)