Files
netdisk-fast-download/parser/doc/security/NASHORN_LIMITATIONS.md
2025-11-30 02:07:56 +08:00

4.4 KiB
Raw Blame History

⚠️ Nashorn引擎限制说明

问题描述

Nashorn JavaScript引擎Java 8-14自带无法中断正在执行的JavaScript代码

这是Nashorn引擎的一个已知限制无法通过编程方式解决。

具体表现

症状

当JavaScript代码包含无限循环时

while(true) {
    var x = 1 + 1;
}

会出现以下情况:

  1. 30秒后客户端收到超时错误
  2. Worker线程继续执行无限循环
  3. 线程被永久阻塞,无法释放
  4. 日志持续输出线程阻塞警告

日志示例

WARN -> [-thread-checker] i.vertx.core.impl.BlockedThreadChecker: 
Thread Thread[playground-executor-1,5,main] has been blocked for 60249 ms, time limit is 60000 ms

为什么无法中断?

尝试过的方案

  1. Thread.interrupt() - Nashorn不响应中断信号
  2. Future.cancel(true) - 无法强制停止Nashorn
  3. ExecutorService.shutdownNow() - 只能停止整个线程池
  4. ScriptContext.setErrorWriter() - 无法注入中断逻辑
  5. 自定义ClassFilter - 无法过滤语言关键字

根本原因

  • Nashorn使用JVM字节码执行JavaScript
  • 无限循环被编译成JVM字节码级别的跳转
  • 没有安全点Safepoint可以插入中断检查
  • while(true) 不会调用任何Java方法完全在JVM栈内执行

现有防护措施

1. 客户端超时(已实现)

executionFuture.toCompletionStage()
    .toCompletableFuture()
    .orTimeout(30, TimeUnit.SECONDS)
  • 30秒后返回错误给用户
  • 用户知道脚本超时
  • 但线程仍被阻塞

2. 前端危险代码检测(已实现)

// 检测无限循环模式
/while\s*\(\s*true\s*\)/gi
/for\s*\(\s*;\s*;\s*\)/gi
  • 执行前警告用户
  • 需要用户确认
  • 依赖用户自觉

3. Worker线程池隔离

  • 使用独立的 playground-executor 线程池
  • 最多16个线程
  • 不影响主服务的事件循环

4. 代码长度限制

  • 最大128KB代码
  • 减少内存消耗
  • 但无法防止无限循环

影响范围

最坏情况

  • 16个恶意请求可以耗尽所有Worker线程
  • 后续所有Playground请求会等待
  • 主服务不受影响(独立线程池)
  • 需要重启服务才能恢复

实际影响

  • 取决于使用场景
  • 如果是公开服务,有被滥用风险
  • 如果是内部工具,风险较低

解决方案

短期方案(已实施)

  1. 前端检测和警告
  2. 超时返回错误
  3. 文档说明限制
  4. ⚠️ 监控线程阻塞告警
  5. ⚠️ 限流已有RateLimiter

中期方案(建议)

  1. 添加IP黑名单机制
  2. 添加滥用检测同一IP多次触发超时
  3. 考虑添加验证码
  4. 定期重启被阻塞的线程池

长期方案(需大量工作)

  1. 迁移到GraalVM JavaScript引擎

    • 支持CPU时间限制
    • 可以强制中断
    • 更好的性能
    • 但需要额外依赖
  2. 使用独立进程执行

    • 完全隔离
    • 可以强制杀死进程
    • 但复杂度高
  3. 代码静态分析

    • 分析AST检测循环
    • 注入超时检查代码
    • 但可能被绕过

运维建议

监控指标

# 监控线程阻塞告警
tail -f logs/*/run.log | grep "Thread blocked"

# 监控超时频率
tail -f logs/*/run.log | grep "JavaScript执行超时"

告警阈值

  • 单个IP 1小时内超时 >3次 → 警告
  • Worker线程阻塞 >80% → 严重
  • 持续阻塞 >5分钟 → 考虑重启

应急方案

# 重启服务释放被阻塞的线程
./bin/stop.sh
./bin/run.sh

用户建议

建议的代码模式

// 使用有限循环
for(var i = 0; i < 1000; i++) {
    // 处理逻辑
}

// 使用超时保护
var maxIterations = 10000;
var count = 0;
while(condition && count++ < maxIterations) {
    // 处理逻辑
}

禁止的代码模式

// 无限循环
while(true) { }
for(;;) { }

// 无退出条件的循环
while(someCondition) {
    // someCondition永远为true
}

// 递归炸弹
function boom() { return boom(); }

相关链接


最后更新: 2025-11-29
状态: ⚠️ 已知限制,已采取缓解措施
建议: 如需更严格的控制考虑迁移到GraalVM JavaScript引擎