Skip to content

Stop Poisoning Bytebuddy #726

Closed
Closed

Description

@NextdoorPsycho

incision: ManualSelfAttach permanently sets jdk.attach.allowAttachSelf=true, breaking agent attachment for other plugins (e.g. ByteBuddy users)

Body:

Summary

taboolib.module.incision.loader.attach.ManualSelfAttach.attach() unconditionally executes:

// 启用 self-attach(JDK 9+)
System.setProperty("jdk.attach.allowAttachSelf", "true")

System.setProperty("jdk.attach.allowAttachSelf", "true")

This property write is process-global and is never reverted. It cannot achieve what the comment intends, and it silently breaks Java agent attachment for every other plugin in the same JVM that uses ByteBuddy (or any library that consults this property at runtime).

Why setting the property at runtime cannot work

HotSpot snapshots this property at JVM startup. sun.tools.attach.HotSpotVirtualMachine reads it via VM.getSavedProperty, not System.getProperty:

static {
    String s = VM.getSavedProperty("jdk.attach.allowAttachSelf");
    ALLOW_ATTACH_SELF = "".equals(s) || Boolean.parseBoolean(s);
}
...
if (!ALLOW_ATTACH_SELF && selfAttach) {
    throw new IOException("Can not attach to current VM");
}

So unless -Djdk.attach.allowAttachSelf=true was passed on the command line, incision's own self-attach fails regardless of the setProperty call. Incision then falls back to its ASM-only path — but the poisoned property remains set for the lifetime of the process.

Why this breaks other plugins

net.bytebuddy.agent.ByteBuddyAgent chooses its attachment strategy from the live system property (ByteBuddyAgent$AttachmentTypeEvaluator$InstallationAction):

if (Boolean.getBoolean("jdk.attach.allowAttachSelf")) {
    return Disabled.INSTANCE; // direct in-process self-attach
}

Normally (property unset), ByteBuddy detects a self-attach and transparently spawns an external helper process to perform the attach, which works fine without any JVM flags. After incision has set the property, ByteBuddy believes in-process self-attach is permitted, skips the external fallback, calls VirtualMachine.attach(<own pid>) directly — and the JDK rejects it with IOException: Can not attach to current VM, because the startup snapshot still says self-attach is disallowed.

Net effect: the JDK ignores the runtime value, ByteBuddy trusts it, and the setProperty call drives the two apart. Any plugin loading after a TabooLib plugin with the incision module can no longer install a Java agent.

TrChat 2.4.10 (TabooLib 6.3.0, incision installed) + Iris on Paper: Iris self-attaches its instrumentation agent via ByteBuddyAgent.attach(...) at startup. Without TrChat this works. With TrChat installed, Iris's attach fails with Can not attach to current VM and it reports its safeguard error, even though the server was started with -XX:+EnableDynamicAgentLoading. Removing TrChat (or adding -Djdk.attach.allowAttachSelf=true so the runtime property matches reality) resolves it.

PLEASE:
Remove the System.setProperty call entirely — it cannot enable self-attach on HotSpot. If there is a VM where it does help, at minimum snapshot the previous value and restore it (including clearing it if it was unset) when the attach attempt fails, so the failed experiment doesn't leak process-global state into unrelated plugins.

You can see my plugin here:
https://github.com/VolmitSoftware/Iris

Thank you


TRANSLATED:

标题:
incision: ManualSelfAttach 永久设置 jdk.attach.allowAttachSelf=true,破坏其他插件的 agent 附加(例如 ByteBuddy 用户)

正文:

摘要

taboolib.module.incision.loader.attach.ManualSelfAttach.attach() 无条件执行:

// 启用 self-attach(JDK 9+)
System.setProperty("jdk.attach.allowAttachSelf", "true")

System.setProperty("jdk.attach.allowAttachSelf", "true")

此属性写入是进程全局的,且从未还原。它无法实现注释中的意图,并会静默破坏同一 JVM 中所有使用 ByteBuddy(或任何在运行时检查此属性的库)的其他插件的 Java agent 附加。

为什么在运行时设置该属性无效

HotSpot 在 JVM 启动时快照此属性。sun.tools.attach.HotSpotVirtualMachine 通过 VM.getSavedProperty 读取它,而不是 System.getProperty:

static {
    String s = VM.getSavedProperty("jdk.attach.allowAttachSelf");
    ALLOW_ATTACH_SELF = "".equals(s) || Boolean.parseBoolean(s);
}
...
if (!ALLOW_ATTACH_SELF && selfAttach) {
    throw new IOException("Can not attach to current VM");
}

因此,除非启动时传入 -Djdk.attach.allowAttachSelf=true,否则 incision 自身的 self-attach 无论是否调用 setProperty 都会失败。Incision 随后回退到纯 ASM 路径,但被污染的属性会在进程生命周期内一直保留。

为什么这会破坏其他插件

net.bytebuddy.agent.ByteBuddyAgent 根据实时系统属性选择附加策略(ByteBuddyAgent$AttachmentTypeEvaluator$InstallationAction):

if (Boolean.getBoolean("jdk.attach.allowAttachSelf")) {
    return Disabled.INSTANCE; // 直接进程内 self-attach
}

正常情况下(属性未设置),ByteBuddy 检测到 self-attach 后会透明地启动一个外部辅助进程来执行附加,无需任何 JVM 参数即可正常工作。当 incision 设置了该属性后,ByteBuddy 认为进程内 self-attach 已允许,跳过外部回退,直接调用 VirtualMachine.attach(<own pid>)——而 JDK 会因启动快照仍禁止 self-attach 而抛出 IOException: Can not attach to current VM。

最终结果:JDK 忽略运行时值,ByteBuddy 信任它,setProperty 调用使两者产生分歧。任何在带有 incision 模块的 TabooLib 插件之后加载的插件都无法再安装 Java agent。

TrChat 2.4.10(TabooLib 6.3.0,安装了 incision)+ Iris on Paper:Iris 在启动时通过 ByteBuddyAgent.attach(...) 自附加其 instrumentation agent。没有 TrChat 时正常。安装 TrChat 后,Iris 的附加失败并报 Can not attach to current VM,即使服务器启动时使用了 -XX:+EnableDynamicAgentLoading。移除 TrChat(或添加 -Djdk.attach.allowAttachSelf=true 使运行时属性与实际一致)即可解决。

请求:
请完全移除 System.setProperty 调用——它无法在 HotSpot 上启用 self-attach。如果有某些 VM 确实有效,至少应快照原值,并在附加失败时恢复(包括原未设置时清除),避免失败的实验泄漏进程全局状态到无关插件。

我的插件在此:
https://github.com/VolmitSoftware/Iris

谢谢

Activity

  1. FxRayHughes commented on Aug 8, 2026

    @FxRayHughes
    Contributor

    感谢报告。我们已独立验证了这个问题,并已在 PR #725 的提交 818f2169 中修复。

    调查结果:

    • HotSpot 的 ALLOW_ATTACH_SELF 在 JVM 启动时通过 VM.getSavedProperty 快照,运行时 System.setProperty 无法真正开启 self-attach。
    • ByteBuddy 会读取实时的 System.getProperty,因此 Incision 的写入会让 ByteBuddy 误判为允许进程内 attach,跳过外部 helper,进而影响 Iris 等其他插件。
    • 这个属性与 Incision 的 JVMTI native backend 无关;删除写入不会影响 JVMTI 的 GetEnv、ClassFileLoadHook 或 RetransformClasses 路径。

    修复内容:

    • 删除 ManualSelfAttach 的运行时属性写入。
    • 只有 JVM 启动参数显式设置 -Djdk.attach.allowAttachSelf=true 时才使用进程内 attach。
    • attach 失败时保持全局属性不变,并继续按现有策略回退 JVMTI。
    • 同步更新 Incision 技术文档。

    我们没有采用“失败后恢复属性”方案,因为并发插件仍可能在恢复前读取到错误值,而且无法区分用户或其他插件原本设置的值。

  2. FxRayHughes commented on Aug 8, 2026

    @FxRayHughes
    Contributor

    更新:由于 PR #725 已在本次提交前合并,属性修复已单独提交到新的 PR #727:
    #727

    PR #727 只包含提交 818f2169,并关联本 Issue。

  3. NextdoorPsycho commented on Aug 8, 2026

    @NextdoorPsycho
    Author

    感谢你们快速修复了 #726 的问题!
    PR #727 已经合并,解决了我遇到的 ByteBuddy 冲突。非常感谢!

  4. FxRayHughes commented on Aug 8, 2026

    @FxRayHughes
    Contributor

    感谢你们快速修复了 #726 的问题! PR #727 已经合并,解决了我遇到的 ByteBuddy 冲突。非常感谢!

    因为这个功能不是最核心的,我们最核心的功能还是 JVMTI native 模式。 本来想作为向下兼容的方案没想到成为了累赘

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