execl crashes C++ node.js-addon
Asked Answered
S

1

7

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)

Somewhere answered 22/11, 2017 at 11:41 Comment(2)
Yes i know, that execl replaces process memory representation. Originally i use it to spawn child process with fork() syscallMawson
Node doesn't support all posix syscall. See this thread #34290903. The crash is expected as you are using something that is not available to youCellobiose
C
1

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.

Cellobiose answered 16/12, 2017 at 5:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.