Copilot commented on code in PR #13146:
URL: https://github.com/apache/trafficserver/pull/13146#discussion_r3734303066


##########
src/api/InkAPI.cc:
##########
@@ -3310,6 +3316,414 @@ TSMgmtUpdateRegister(TSCont contp, const char 
*plugin_name, const char *plugin_f
   global_config_cbs->insert(reinterpret_cast<INKContInternal *>(contp), 
plugin_name, plugin_file_name);
 }
 
+////////////////////////////////////////////////////////////////////
+//
+// Config Registry - plugin config reload registration
+//
+////////////////////////////////////////////////////////////////////
+
+namespace
+{
+// Handle behind the opaque TSCfgLoadCtx. One is created per handler invocation
+// (and per subtask) and owned by PluginCtxRegistry. Caches the strings/nodes 
the
+// getters need to return as stable pointers for the lifetime of the handle.
+struct PluginConfigContext {
+  ConfigContext ctx;
+  std::string   filename;
+  std::string   reload_token;
+  YAML::Node    supplied_yaml;
+  YAML::Node    reload_directives;
+};
+
+DbgCtl dbg_ctl_plugin_config{"config.reload"};
+
+// Process-lifetime registry of live TSCfgLoadCtx handles.
+//
+// Handles handed to plugins are opaque monotonically-increasing ids, never raw
+// pointers, so a stale handle can never alias a freshly-allocated context (no
+// ABA). Every accessor validates the id under the registry lock, so a call 
made
+// after Complete/Fail - or on a bogus handle - is a genuine no-op instead of a
+// use-after-free read. Complete/Fail extract (and thus free) the context under
+// the same lock, so a second finalize can never double-free.
+class PluginCtxRegistry
+{
+public:
+  static PluginCtxRegistry &
+  instance()
+  {
+    static PluginCtxRegistry r;
+    return r;
+  }
+
+  /// Take ownership of @a pctx and return its opaque handle.
+  TSCfgLoadCtx
+  create(std::unique_ptr<PluginConfigContext> pctx)
+  {
+    std::lock_guard lock(_mutex);
+    uint64_t        id = _next_id++;
+    _live.emplace(id, std::move(pctx));
+    return reinterpret_cast<TSCfgLoadCtx>(static_cast<uintptr_t>(id));
+  }
+
+  /// Look up a live context by handle. Returns @c nullptr when the handle was
+  /// already finalized or was never valid. The returned pointer stays owned by
+  /// the registry; callers must use it only within the (single-threaded per
+  /// handle) plugin contract.
+  PluginConfigContext *
+  lookup(TSCfgLoadCtx handle)
+  {
+    uint64_t        id = 
static_cast<uint64_t>(reinterpret_cast<uintptr_t>(handle));
+    std::lock_guard lock(_mutex);
+    auto            it = _live.find(id);
+    return it != _live.end() ? it->second.get() : nullptr;
+  }

Review Comment:
   PluginCtxRegistry::lookup() returns a raw PluginConfigContext* after 
releasing the registry mutex. All TSCfgLoadCtx* APIs then dereference this 
pointer without holding any lifetime guarantee. If a plugin calls 
TSCfgLoadCtxComplete/Fail on another thread while a different thread is in (or 
about to enter) TSCfgLoadCtxAddLog/InProgress/Get* for the same handle, 
extract() can free the context and the other thread can hit a use-after-free.
   
   Given the API explicitly supports deferred completion from another thread, 
the handle operations should be safe under concurrent use (even if plugins 
misbehave occasionally). Consider returning a shared ownership token from the 
registry (e.g., store std::shared_ptr<PluginConfigContext> in _live and have 
lookup() return a shared_ptr), or providing a registry method that executes an 
accessor under the mutex so the context cannot be freed mid-call.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to