Skip to content

StretchyList::push_back() can corrupt memory: cons cell unprotected across Rf_install() #1489

Description

@kevinushey

In the named-object variant of StretchyList::push_back(), the freshly allocated cons cell is neither protected nor reachable when the Symbol constructor runs Rf_install():

https://github.com/RcppCore/Rcpp/blob/master/inst/include/Rcpp/api/meat/StretchyList.h#L36-L45

StretchyList_Impl<StoragePolicy>& StretchyList_Impl<StoragePolicy>::push_back__impl( const T& obj, traits::true_type ){
    Shield<SEXP> s( wrap(obj.object) ) ;
    SEXP tmp  = Rf_cons( s, R_NilValue );
    Symbol tag  = obj.name ;   // Rf_install() -- can allocate and trigger GC
    SET_TAG(tmp, tag) ;
    SEXP self = Storage::get__() ;
    SETCDR( CAR(self), tmp) ;
    SETCAR( self, tmp ) ;
    return *this ;
}

If the tag name is not yet interned, Rf_install() allocates (mkChar() plus the SYMSXP), which can trigger a garbage collection. At that point tmp is only referenced from the C stack, so it is collected; SET_TAG() then writes into a reclaimed node, and SETCDR() / SETCAR() link that reclaimed node into the (preserved) list.

Note that the named variant of push_front() in the same file gets the ordering right, constructing the Symbol before calling Rf_cons(), so only push_back() is affected.

Reproducible example

writeLines('
#include <Rcpp.h>
using namespace Rcpp;

// [[Rcpp::export]]
SEXP stretchy_push_back(std::string name) {
    StretchyList out;
    out.push_back(Named(name, 42));
    return out;
}
', "repro.cpp")

Rcpp::sourceCpp("repro.cpp")

# a symbol name that has never been interned in this session, so that
# Rf_install() inside push_back() has to allocate
name <- paste(sample(c(letters, LETTERS), 32, TRUE), collapse = "")

gctorture(TRUE)
x <- stretchy_push_back(name)
gctorture(FALSE)

cat("typeof:", typeof(x), "\n")
str(x)

Output:

typeof: symbol 
 symbol oZoNOOQgnEmozfyTBxxQsXnamjjNqEYY

Under gctorture(), the GC triggered inside Rf_install() collects the cons cell, and its node is immediately reused for the newly created SYMSXP -- so the "pairlist" we get back is actually the symbol itself. Expected output would be a one-element pairlist, e.g.:

typeof: pairlist 
Dotted pair list of 1
 $ <name>: num 42

Depending on allocation timing this can also surface as corruption of unrelated objects (the reclaimed node can be reused for anything), so the symptom in real code would typically be a segfault or silently corrupted data rather than this tidy misclassification.

Observed with Rcpp master (3a9aeaa) on R 4.6.1.

A PR with a fix (reordering push_back__impl() to construct the Symbol before Rf_cons(), matching push_front__impl()) will follow shortly.

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