Node.js 性能平台支持死循环和正则攻击定位

Node.js 应用里面,常见性能问题从表现上来看有这么几类(Node.js 性能平台都提供了对应解决方案):

  • CPU 飚高:做 CPU Prfiling 定位热点函数
  • 内存泄露:堆快照/heaptimeline/heapprofile进行定位
  • CPU/内存使用都不高,但是 QPS 上不去或者 RT 很长:trace进行定位

CPU 飚高的情况又可分为两类:

  • 仍然可以继续处理业务,只是 RT 变长,这可以通过 CPU Profiling 定位
  • 完全无法继续处理业务,表现为 CPU 100% 占用,堆信息监控为直线

无法处理新业务的情况,JS 代码一般处于下面几种状态:

  • while/for 循环一直运行
  • 计算一直无法完成:例如执行一个斐波那契数列运算,当输入大于 50 的时候
  • 执行长时间正则表达式,

Node.js 性能平台在运行时v3.11.3v4.2.1及以前的版本里面对于,由于无法启动 CPU Profiling,也没有特别好的定位方法。只能根据监控提供问题开始时刻,去 nginx 的 access 日志里面查找相关线索。

随着 v3.11.4v4.2.1版本的发布,这两大难题将不会继续困扰我们。

即使代码处于上述中死循环长时间计算或者长时间正则表达式匹配状态,仍然可以启动 CPU Profiling,定位到问题所在。

下面的例子都来自不同行业的用户案例,为了简化验证过程,集成到了一个简单的 http 服务中。
默认用户已经开通 Node.js 性能服务,否则请参考 用户指南

'use strict';

const http = require('http');

function regexp() {
  let str = '<br/>                                             ' +
    '           早餐后*活动,于指定时间集合自行办理退房手续。';
  str += '<br/>                                      <br/>' +
    '                                        <br/>           ' +
    '                         <br/>';
  str += '                                    <br/>' +
    '                                                        ' +
    '                                                        ' +
    '        <br/>';
  str += '                                                <br/>                                                                                                                <br/>';
  str += '                                                     ' +
    '                                                        ' +
    '       根据船班时间,自行前往暹粒机场,返回中国。<br/>';
  str += '如需送机服务,需增加280/每单。<br/>';

  let r = String(str).replace(/(^(\s*?<br[\s\/]*?>\*?)+|(\s*?<br[\s\/]*?>\s*?)+?$)/igm, '');
}

function deadloop () {
  while(1);
}

function f1() {
  deadloop();
}
function f2(){
  f1();
}
function f3(){
  f2();
}
function f4(){
  f3();
}

http.createServer( (req, res) => {
  console.log(req.url);

  switch(req.url) {
    case '/regexp': {
      regexp();
      res.end('regexp case');
    } break;
    case '/deadloop': {
      f4();
      res.end('deadloop case');
    } break;
    case '/exit': {
      process.exit(0);
    } break;
    default: {
      res.writeHead(200, "OK",{'Content-Type': 'text/html'});
      res.write('<html><head><title>Node.js</title></head><body style="font-family:arial;">');
      res.write('<h2> Regular Expression and Dead Loop Detection Example</h2>');

      res.write('<p>Click on button below to trigger long time running regexp test.');
      res.write('<form enctype="application/x-www-form-urlencoded" action="/regexp" method="post">');
      res.write('<button>Trigger Long Running Regular Expression</button></form>');

      res.write('<p>Click on button below to enter JavaScript dead loop test');
      res.write('<form enctype="application/x-www-form-urlencoded" action="/deadloop" method="post">');
      res.write('<button>Trigger Dead Loop JS function</button></form>');

      res.write('<p>The test can be terminated only before the above cases triggered.');
      res.write('<form enctype="application/x-www-form-urlencoded" action="/exit" method="post">');
      res.write('<button>Exit Test</button></form>');
      res.write('</form></body></html');
      res.end();
    } break;
  }
}).listen(8080);

console.log('\nhttp listen at 8080 pid: ', process.pid);

以下两幅图均是代码已经处于 CPU 100% 状态后启动 Profiling 后定位结果。

  • 正则表达式准确定位到当前执行的函数

Node.js 性能平台支持死循环和正则攻击定位

  • 死循环目前定位到 while(1) 所在函数的调用者。如果在 CPU 100% 前启动 Profiling 则可以定位到具体函数,然而线上故障的不可预测性使得我们无法在问题出现前3分钟启动,后续 Node.js 性能平台会继续优化此处。

Node.js 性能平台支持死循环和正则攻击定位

注意:
v3.11.4 和 v4.2.1 运行时采用了 SIGUSR2 触发诊断。如果用户没有设置 ENABLE_NODE_LOG=YES,请不要点击故障诊断按钮,否则会造成进程 crash。 其它版本没有这个问题。

上一篇:如何针对性系统学习Web安全成为一名黑客儿?(建议收藏 ❤️)


下一篇:《Linux多线程服务端编程:使用muduo C++网络库》上市半年重印两次,总印数达到了9000册