Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

execl crashes C++ node.js-addon

As normal C++ execl works fine (compiling with g++ ok.cc -o ok.elf)

#include <unistd.h>
int main(){
  execl("/usr/bin/python", "/usr/bin/python", nullptr);
}

But crashes, when works as node.js C++ addon

#include <node.h>
#include <unistd.h>

namespace bug{
  void wtf(const v8::FunctionCallbackInfo<v8::Value>& args){
    execl("/usr/bin/python", "/usr/bin/python", nullptr);
  }

  void init(v8::Local<v8::Object> exports){
    NODE_SET_METHOD(exports, "wtf", bug::wtf);
  }
  NODE_MODULE(NODE_GYP_MODULE_NAME, init)
}

Crash of node.js extension

node.js v8.9.1
node-gyp v3.6.2
gcc version 6.3.0 20170406 (Ubuntu 6.3.0-12ubuntu2)

like image 890
Евгений Новиков Avatar asked Nov 22 '17 11:11

Евгений Новиков


1 Answers

Node doesn't support all posix syscall.

See this thread

A way to call execl, execle, execlp, execv, execvP or execvp from Node.js

The crash is expected as you are using something that is not available to you. As discussed in the above thread you need to either create your own exec

index.cc

#include <nan.h>
#include <fcntl.h>
#include <unistd.h>

int doNotCloseStreamsOnExit(int desc) {
  int flags = fcntl(desc, F_GETFD, 0);
  if (flags < 0) return flags;
  flags &= ~FD_CLOEXEC; //clear FD_CLOEXEC bit
  return fcntl(desc, F_SETFD, flags);
}

void copyArray(char* dest[], unsigned int offset, v8::Local<v8::Array> src) {
  unsigned int length = src->Length();
  for (unsigned int i = 0; i < length; i++) {
    v8::String::Utf8Value arrayElem(Nan::Get(src, i).ToLocalChecked()->ToString());
    std::string arrayElemStr (*arrayElem);
    char* tmp = new char[arrayElemStr.length() +1];
    strcpy(tmp, arrayElemStr.c_str());
    dest[i + offset] = tmp;
  }
}

void setEnv(v8::Local<v8::Array> src) {
  unsigned int length = src->Length();
  v8::Local<v8::String> keyProp = Nan::New<v8::String>("key").ToLocalChecked();
  v8::Local<v8::String> valueProp = Nan::New<v8::String>("value").ToLocalChecked();
  for (unsigned int i = 0; i < length; i++) {
    v8::Local<v8::Object> obj = Nan::Get(src, i).ToLocalChecked()->ToObject();

    v8::String::Utf8Value objKey(Nan::Get(obj, keyProp).ToLocalChecked()->ToString());
    v8::String::Utf8Value objValue(Nan::Get(obj, valueProp).ToLocalChecked()->ToString());

    std::string objKeyStr (*objKey);
    char *key = const_cast<char*> ( objKeyStr.c_str() );
    std::string objValueStr (*objValue);
    char *value = const_cast<char*> ( objValueStr.c_str() );

    setenv(key, value, 1);
  }
}

void Method(const Nan::FunctionCallbackInfo<v8::Value>& info) {
  if (info.Length() < 3) {
    return;
  }
  if (!info[0]->IsString()) {
    return;
  }

  // get command
  v8::String::Utf8Value val(info[0]->ToString());
  std::string str (*val);
  char *command = const_cast<char*> ( str.c_str() );

  // set env on the current process
  v8::Local<v8::Array> envArr = v8::Local<v8::Array>::Cast(info[1]);
  setEnv(envArr);

  // build args: command, ...args, NULL
  v8::Local<v8::Array> argsArr = v8::Local<v8::Array>::Cast(info[2]);
  char* args[argsArr->Length() + 2];
  args[0] = command;
  copyArray(args, 1, argsArr);
  args[argsArr->Length() + 1] = NULL;

  // fix stream flags
  doNotCloseStreamsOnExit(0); //stdin
  doNotCloseStreamsOnExit(1); //stdout
  doNotCloseStreamsOnExit(2); //stderr

  execvp(command, args);
}

void Init(v8::Local<v8::Object> exports) {
  exports->Set(Nan::New("exec").ToLocalChecked(),
               Nan::New<v8::FunctionTemplate>(Method)->GetFunction());
}

NODE_MODULE(exec, Init)

index.js

'use strict';

var addon = require('bindings')('addon');
var path = require('path');
var fs = require('fs');

module.exports = function(cmd, env, args) {
  if (!cmd) {
    throw new Error('Command is required');
  }

  var envArr = Object.keys(env || {}).map(key => {
    return {
      key,
      value: env[key],
    };
  });

  addon.exec(cmd, envArr, args || []);
};

PS: Code posted from https://github.com/OrKoN/native-exec, in case link goes inactive in future

Also another thing that you shouldn't try to do is execute something which needs to get the TTY, you will add a lot of complexity in that case. So running python will need to take control of your TTY.

like image 110
Tarun Lalwani Avatar answered Sep 27 '22 19:09

Tarun Lalwani