}
void show_config(ceph::Formatter* f) const;
- seastar::future<> parse_argv(std::vector<const char*>& argv) {
- // we could pass whatever is unparsed to seastar, but seastar::app_template
- // is used for driving the seastar application, and
- // crimson::common::ConfigProxy is not available until seastar engine is up
- // and running, so we have to feed the command line args to app_template
- // first, then pass them to ConfigProxy.
- return do_change([&argv, this](ConfigValues& values) {
- get_config().parse_argv(values,
- obs_mgr,
- argv,
- CONF_CMDLINE);
+ seastar::future<> parse_argv(std::vector<std::string> args) {
+ // Take args by value so the lambda can safely be invoked on another shard
+ return do_change([args = std::move(args), this](ConfigValues& values) {
+ std::vector<const char*> argv;
+ argv.reserve(args.size());
+ for (const auto& s : args) argv.push_back(s.c_str());
+ get_config().parse_argv(values, obs_mgr, argv, CONF_CMDLINE);
});
}
--- /dev/null
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:nil -*-
+// vim: ts=8 sw=2 expandtab
+//
+// Minimal unit test demonstrating stack-use-after-return when passing
+// a reference to a local variable into a lambda that runs on another shard
+// via invoke_on to replicate the pattern that ConfigProxy::parse_argv had before
+// the fix (taking vector<const char*>& and capturing by reference in do_change)
+//
+// The old path captures [&argv]; the lambda runs on shard 1 after the caller
+// on shard 0 has returned, so argv (on shard 0's stack) is destroyed
+//
+// Run: ./bin/unittest-seastar-config-parse-argv-uaf --smp 2
+// Without ASan stack-use-after-return: may crash (SIGABRT) due to UAF
+// With ASAN_OPTIONS=detect_stack_use_after_return=1: may detect the UAF
+//
+// Build: ninja unittest-seastar-config-parse-argv-uaf
+//
+
+#include <seastar/core/app-template.hh>
+#include <seastar/core/sharded.hh>
+#include <seastar/core/smp.hh>
+#include "test/crimson/ctest_utils.h"
+
+using namespace seastar;
+
+struct TargetService {};
+
+static sharded<TargetService>* g_svc = nullptr;
+
+// Mimics the old ConfigProxy::parse_argv pattern: takes reference, captures
+// in lambda sent to another shard. When lambda runs, caller's stack is gone.
+static future<> buggy_parse_argv_like(std::vector<const char*>& argv) {
+ return g_svc->invoke_on(1, [&argv](TargetService&) {
+ volatile size_t n = argv.size(); // use-after-return: argv was on caller's stack
+ (void)n;
+ });
+}
+
+// New approach: take by value, move into closure so data outlives the call
+static future<> correct_parse_argv_like(std::vector<std::string> args) {
+ return g_svc->invoke_on(1, [args = std::move(args)](TargetService&) {
+ volatile size_t n = args.size(); // safe: args is captured by value
+ (void)n;
+ });
+}
+
+static future<> test_uaf()
+{
+ if (smp::count < 2) {
+ std::cerr << "Skip: need --smp 2 to demonstrate cross-shard use-after-return" << std::endl;
+ return make_ready_future<>();
+ }
+
+ // Run the old call from shard 0. The lambda is sent to shard 1.
+ // By the time shard 1 runs it, the caller on shard 0 has returned and
+ // 'argv' (on shard 0's stack) is gone.
+ return smp::submit_to(0, [] {
+ std::vector<const char*> argv = {"--foo=1", "--bar=2"};
+ return buggy_parse_argv_like(argv);
+ });
+}
+
+static future<> test_correct()
+{
+ if (smp::count < 2) {
+ return make_ready_future<>();
+ }
+ std::vector<std::string> args = {"--foo=1", "--bar=2"};
+ return correct_parse_argv_like(std::move(args));
+}
+
+int main(int argc, char** argv)
+{
+ app_template app{get_smp_opts_from_ctest()};
+ static sharded<TargetService> svc; // static so it outlives the async chain
+ return app.run(argc, argv, [&] {
+ return svc.start().then([] {
+ g_svc = &svc;
+ return test_correct();
+ }).then([] {
+ return test_uaf();
+ }).finally([] {
+ g_svc = nullptr;
+ return svc.stop();
+ }).then([] {
+ std::cout << "Done. With ASAN_OPTIONS=detect_stack_use_after_return=1, "
+ << "the buggy path may trigger use-after-return detection." << std::endl;
+ }).handle_exception([](auto e) {
+ std::cerr << "Test failed: " << e << std::endl;
+ return make_exception_future<>(e);
+ });
+ });
+}