Search code examples
linuxcrashnode.js-addonunistd.h

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)


Solution

  • 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.