镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

calling a module function with too few arguments reads uninitialized memory #1521

Closed

Description

@kevinushey

Functions exposed through a module with function() don't check how many arguments they were called with. Calling one with too few arguments reads the missing ones from uninitialized memory, which usually segfaults.

The R wrapper built by .get_Module_function() (R/Module.R) is function(...) .External(InternalFunction_invoke, fun_pointer, ...), so R itself doesn't enforce the arity. InternalFunction_invoke (src/module.cpp) unpacks the supplied arguments into a stack array of MAX_ARGS (65) SEXPs with UNPACK_EXTERNAL_ARGS (src/internal.h), which only initializes the first nargs slots, and then calls the function without looking at nargs:

SEXP InternalFunction_invoke(SEXP args) {
BEGIN_RCPP
    SEXP p = CDR(args);
    XP_Function fun(CAR(p)); p = CDR(p);
    UNPACK_EXTERNAL_ARGS(cargs,p)
    return fun->operator()(cargs);
END_RCPP
}

CppFunctionN::operator() then converts args[0] through args[sizeof...(T) - 1], so every parameter past the ones supplied is an uninitialized pointer handed to as<T>().

The same happens when formals are supplied with function(name, fun, formals) but the list is shorter than the C++ parameter list: every call then passes too few arguments.

Methods and constructors are not affected, because their default validators (yes_arity<n>) check the count before dispatching. The (unused) Module::invoke() code path also has the check (if( fun->nargs() > nargs ) throw std::range_error( "incorrect number of arguments" )); it just never made it into InternalFunction_invoke.

Reproducible example

repro.cpp:

#include <Rcpp.h>

using namespace Rcpp;

double add(double x, double y) {
    return x + y;
}

RCPP_MODULE(arity_module) {
    function("add", &add);
}

repro.R:

Rcpp::sourceCpp("repro.cpp")
cat("module loaded\n")

# add() takes two arguments but is called with one
res <- tryCatch(add(1), error = function(e) conditionMessage(e))
cat("result:", res, "\n")

Output with R 4.6.1 (macOS 27, Apple clang 21, Rcpp 1.1.2.6 / current master):

module loaded

 *** caught segfault ***
address 0x100000001, cause 'invalid permissions'

Traceback:
 1: .External(list(name = "InternalFunction_invoke", ...), <pointer: 0x74ed11c000>, ...)
 ...
An irrecoverable exception occurred. R is aborting now ...
Segmentation fault: 11     Rscript repro.R

The crash reproduced on every run; depending on what happens to be on the stack, the call can instead fail with a confusing conversion error (e.g. Not compatible with requested type: [type=unknown type #12; target=double]) or silently use a garbage value.

I'll follow up with a PR that checks the argument count against the exposed function's arity before calling it and raises an R error when too few arguments are supplied. Extra arguments are currently ignored, and I'd keep that as-is to avoid breaking existing callers.

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