ssh2-sftp-client: 100% reproducible bug that results in node process crash when using promises (bug in promise reject)

Version: 9.0.4 Node: v18.12.0 OS: Mac OS 12.1

Problem manifests itself if network becomes inaccessible after Client.connect was already successfully executed but before Client.put method is called.

Code Snippet:

let Client = require('ssh2-sftp-client');
let sftp = new Client();

function sleep(ms) {
  return new Promise((r) => setTimeout(r, ms));
}

async function run() {
  const startTime = Date.now();
  const filename = `random-file-name-${startTime}`;

  const fileData = Buffer.from('TRACER FILE');
  const remotePath = `incoming/${filename}`;

  try {
    console.log(`---- Before connect ----- `);

    await sftp.connect({
      host: '1.1.1.1',
      port: 22,
      username: 'user',
      password: 'password',
    });

    console.log(`---- After connect ----- `);
    
    console.log(`---- Sleep for 20s ----- `);

    // !!!!!!!   BREAK CONNECTION HERE while sleeping
    await sleep(20000);

    console.log(`---- Before put ----- `);
    const result = await sftp.put(fileData, remotePath);
    console.log(`---- After put ----- `);
    console.log(`SFTP put result: ${result}`);
    sftp.end();
  } catch (err) {
    console.error(`Error: ${err.message}`);
  }

  await sleep(7000);

  console.log('The End');
}

run();

Log from the run that completes successfully:

---- Before connect ----- 
---- After connect ----- 
---- Sleep for 20s ----- 
---- Before put ----- 
---- After put ----- 
SFTP put result: Uploaded data stream to incoming/random-file-name-1670562812521
The End

Log from the run that crashes with disconnect during sleep(20000):

---- After connect ----- 
---- Sleep for 20s ----- 
---- Before put ----- 
/project/node_modules/ssh2-sftp-client/src/utils.js:25
      throw newError;
      ^

Error: put: read ETIMEDOUT
    at Client.fn (/project/node_modules/ssh2-sftp-client/src/utils.js:20:22)
    at Client.emit (node:events:525:35)
    at Socket.<anonymous> (/project/node_modules/ssh2/lib/client.js:745:12)
    at Socket.emit (node:events:513:28)
    at emitErrorNT (node:internal/streams/destroy:151:8)
    at emitErrorCloseNT (node:internal/streams/destroy:116:3)
    at process.processTicksAndRejections (node:internal/process/task_queues:82:21) {
  code: 'ETIMEDOUT'
}

Node.js v18.12.0

It looks that for some reason in this use-case errorListener method in utils.js is invoked with reject parameter set to undefined resulting in exception vs promise reject.

How to break connection for MacOS:

# PF should be started: sudo pfctl -E
# Check current ruleset: sudo pfctl -sr
# To Apply this ruleset file: sudo pfctl -f ./pf-block-ip.conf
# To Flush (all) rulesets: sudo pfctl -F rules

# Block incoming traffic from some IP
block in quick from 1.1.1.1

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 24 (10 by maintainers)

Most upvoted comments

Tim,

I’d like to emphasize the fact, that this crash is 100% reproducible and not dependent on any variations. You just need to run the snippet I provided, drop connection during the sleep and you’ll reproduce the problem, I’m absolutely sure about this. I strongly recommend to run the snippet at least once - you’ll instantly see the issue.

I"m not questioning the fact it crashes. However, I’m not sure your conclusion is correct. As I stated before, I won’t have time to look at this in any detail until after the new year.

If you can diagnose further or if you want to provide a PR, that would be great, otherwise, you will have to wait until I get to it.