Repository navigation
Description
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