Remote script execution on Pi

I ran a “whoami” command with this interface, and the processes run as root. This gives an overwhelmingly dangerous level of control to the firmware, and could allow for arbitrary code execution resulting in installation of compromising software if the firmware accepts user input by cloud functions or other mechanisms. This could open up poorly coded firmwares to something akin to the IoT botnets we saw wreaking havoc lately with DynDNS. Are there plans in the future to at least drop the firmware process to a less-privileged user? Running as an unprivileged user may be a hinderance, but it might be a good idea in the name of safety.

1 Like

The Raspberry Pi GPIO and low-level processes can only be accessed by root, and it because of this why the firmware must run as root.

If your Particle account or access token is compromised then so are all of the devices linked to it. Particle is trying to make their platform as secure as possible to reduce these risks.

Regarding the need for low-level access; that’s fair enough.

However, I can think of many ways this could go awry without having a compromised account / token. The use of webhooks or TCPClient to pull data in from other servers, and then using the retrieved data within system calls / Process::run() could open up the opportunity for a compromised web service (outside of Particle’s control) to execute commands on a Pi.

Granted, the attacker would need to know a lot of specific details to execute such a compromise… across multiple platforms and unique firmwares, but it’s still technically “possible”. Food for thought.

1 Like

It is very possible. Say you are polling data from somewhere and one day ; rm -rf -no-preserve-root / is attached to the response string and called inside of Process::run(). The firmware would happily go erase your entire SD card. There are even worse commands that could be run and it would just require ; and then the command for them to be executed.

1 Like

As @nrobinson2000 mentioned, the GPIO access is done by direct memory writes to the GPIO registers which can only be done by root.

The Process::run can run commands as other users by doing Process::run("sudo -u pi my_command"); This would prevent accidentally running commands that affect the system. This can be added to the docs, or this behavior could be the default, unless someone does Process::run_sudo() maybe. What do you think?

2 Likes

Would it be better to have two, or maybe three overrides for Process::run()?

The default (as it exists already) could be modified to run as a less privileged user via an implied sudo -u ... (maybe nobody, or even pi? Although some people might remove pi on their device… and nobody would severely limit the amount of functionality available)

Process::run("command");

The second override allows you to specify the name of the user you wish to run as by string:

Process::run("command", "root");

Optionally, the third override allows you to specify the UID of the user you wish to run as by number:

Process::run('command', 0);

NOTE: The problem with using sudo is that it breaks the usual STDIN/STDOUT/STDERR piping semantics. We would need to handle this silently, likely by running sudo -u {{user}} sh -c '{{command}}', so that it doesn’t cause headaches for the users of this functionality. We’d also need to escape single quotes when necessary…

2 Likes

Process::run already runs commands through sh -c {{command}} and the piping is already customized to be able to provide input / capture output. So I don’t expect issues with doing sudo -u {{user}} within Process::run.

2 Likes

I like the overrides you are suggesting, especially:

Process::run("command", "user");

I think this would be good notation for people that are less familiar with bash, and it wouldn’t require them to type sudo -u

Of course, the ability to specify a user manually also opens up the ability to specify a non-existent user; I expect that sudo would probably be the one to break that news unless the firmware checks first. I guess it depends where the error should be handled.

1 Like

So while we’re talking about security…let’s think about if your particle.io acount was compromised.

All of these talks about overrides for Process::run would be pointless as the attacker would be able to re-flash the firmware. (Not that there isn’t good discussion on the potential for using retrieved information to run in scripts…)

It might be nice to provide some information about how and why it might be a good idea to disable OTA updates with System.disableUpdates() – maybe even with some tutorial on how one could program in failsafes for enabling OTA updates.

I mean it’s all unlikely, but you still generally want to make yourself a harder target than the next guy right? So maybe a tool-tip type thing in the docs/code guide to reflect this would be helpful – don’t want to scare people, but also want them to be informed.

2 Likes

Hello,

This example code by @jvanier for Process::run() is great. It worked very well just a couple of weeks ago (early or mid May 2017) but now has stopped compiling for a Raspberry Pi target.

The example code clip at the very top of this post, copy-pasted generates this compiler error:

processexample.ino:7:3: error: 'Process' was not declared in this scope
   Process proc = Process::run("vcgencmd measure_temp");
   ^
processexample.ino:7:11: error: expected ';' before 'proc'
   Process proc = Process::run("vcgencmd measure_temp");
       ^
processexample.ino:8:3: error: 'proc' was not declared in this scope
   proc.wait();
   
Error: Could not compile. Please review your code.

The compiler error text is formatted nicely in the web IDE but otherwise it appears the Process #include header is no longer on the compiler line.

The complete raw compiler error is here:

Processing processexample.ino make -C ../newlib_nano make[1]: Entering directory '/firmware/newlib_nano' make[1]: Nothing to be done for 'all'. make[1]: Leaving directory '/firmware/newlib_nano' make -C ../user make[1]: Entering directory '/firmware/user' Building cpp file: processexample.cpp Invoking: ARM GCC CPP Compiler mkdir -p ../build/target/user/platform-0-lto arm-none-eabi-gcc -DSTM32_DEVICE -DSTM32F10X_MD -DPLATFORM_THREADING=0 -DPLATFORM_ID=0 -DPLATFORM_NAME=core -DUSBD_VID_SPARK=0x1D50 -DUSBD_PID_DFU=0x607F -DUSBD_PID_CDC=0x607D -DSPARK_PLATFORM -DFLASHEE_EEPROM -g3 -gdwarf-2 -Os -mcpu=cortex-m3 -mthumb -flto -DINCLUDE_PLATFORM=1 -DPRODUCT_ID=0 -DPRODUCT_FIRMWARE_VERSION=65535 -DUSE_STDPERIPH_DRIVER -DDFU_BUILD_ENABLE -DSYSTEM_VERSION_STRING=0.6.2 -DRELEASE_BUILD -I./inc -I../wiring/inc -I../system/inc -I../services/inc -I../communication/src -I../hal/inc -I../hal/shared -I../hal/src/core -I../hal/src/stm32 -I../platform/shared/inc -I../platform/MCU/STM32F1xx/STM32_StdPeriph_Driver/inc -I../platform/MCU/STM32F1xx/STM32_USB_Device_Driver/inc -I../platform/MCU/STM32F1xx/SPARK_Firmware_Driver/inc -I../platform/MCU/shared/STM32/inc -I../platform/MCU/STM32F1xx/CMSIS/Include -I../platform/MCU/STM32F1xx/CMSIS/Device/ST/Include -I../platform/NET/CC3000/CC3000_Host_Driver -I../dynalib/inc -I -I./libraries -I -I -I -I -I. -MD -MP -MF ../build/target/user/platform-0-ltoprocessexample.o.d -ffunction-sections -fdata-sections -Wall -Wno-switch -Wno-error=deprecated-declarations -fmessage-length=0 -fno-strict-aliasing -DSPARK=1 -DPARTICLE=1 -DSTART_DFU_FLASHER_SERIAL_SPEED=14400 -DSTART_YMODEM_FLASHER_SERIAL_SPEED=28800 -DSPARK_PLATFORM_NET=CC3000 -fno-builtin-malloc -fno-builtin-free -fno-builtin-realloc -DLOG_INCLUDE_SOURCE_INFO=1 -DPARTICLE_USER_MODULE -DUSE_THREADING=0 -DUSE_SPI=SPI -DUSE_CS=A2 -DUSE_SPI=SPI -DUSE_CS=A2 -DUSE_THREADING=0 -DMODULE_VERSION=0 -DMODULE_FUNCTION=3 -DMODULE_DEPENDENCY=0,0,0 -D_WINSOCK_H -D_GNU_SOURCE -DLOG_MODULE_CATEGORY="\"app\"" -fno-exceptions -fno-rtti -fcheck-new -std=gnu++11 -c -o ../build/target/user/platform-0-ltoprocessexample.o processexample.cpp processexample.ino: In function 'void loop()': processexample.ino:7:3: error: 'Process' was not declared in this scope Process proc = Process::run("vcgencmd measure_temp"); ^ processexample.ino:7:11: error: expected ';' before 'proc' Process proc = Process::run("vcgencmd measure_temp"); ^ processexample.ino:8:3: error: 'proc' was not declared in this scope proc.wait(); ^ ../build/module.mk:267: recipe for target '../build/target/user/platform-0-ltoprocessexample.o' failed make[1]: Leaving directory '/firmware/user'

Is this example code clip still compiling on your end? Is there an include missing or is there a way to add it?

The web IDE is really cool but provides no way to grep the headers.

Marc

Yes, this still compiles, but your error output does suggest you are not building for a RPi, but for Spark Core

platform-0 stands for Spark Core, RPi would be platform-31

Double check that you have selected Raspberry Pi as target in the Device drawer.

Hello @ScruffR,

Perfect. Your help solved this issue. It compiles just fine once the target device is selected. I recently added another device and had not figured out how to target one versus another.

With no device selected (i.e. no yellow star on the devices pane) the compiler defaults to spark, just as you pointed out.

By clicking the star on the left hand side of a device, it selects that device for compilation and targeting. That selection includes the target-specific libraries during compiling and linking. I didn’t know platform-0 means spark and platform-31 means Raspberry Pi. Good to know.

Image illustrating the selection:

Thanks for the careful consideration and discovering this problem.

Marc

1 Like

Hi everyone,

This is my first entry since I’m new to particle.
Hope someone can answer.

I managed to execute commands either using the
Process proc = Process::run(“command”);
proc.wait();
or the simple system(“command”)

This works fine with commands like shutdown now/restart now

Now I’m trying to open a browser window using the execution lines.
Why doesn’t it work with e.g. xdg-open URL ?

Sorry if this is a rather stupid question,
Best regards,
Flo

I think you need to set the DISPLAY variable in bash.

Kind of like this:

You could probably use

Process::run("DISPLAY=:0 xdg-open ...")

Thanks for the quick reply nrobinson!
I tried and it doesn't work.
Also running python scripts doesn't show any results.
This might be a matter of my setup:

I run the Google Assistant SDK on my Raspi 3
Then, in IFTTT I setup an applet the uses the GA (with one text ingredient) as trigger and publishes an event to Particle. In Particle the text ingredient determines which if function is to be exexuted back on my RasPi.

 if (strcmp(data,"off")==0) {
    // if GA text ingredient is "off"
    digitalWrite(led,LOW);
    digitalWrite(boardLed,LOW);
  }
  else if (strcmp(data,"on")==0) {
    // if GA text ingredient is "on"
    digitalWrite(led,HIGH);
    digitalWrite(boardLed,HIGH);
  }
  else if (strcmp(data,"open")==0) {
    // if GA text ingredient is "open"
    Process proc = Process::run("DISPLAY=:0 xdg-open http://google.de");
    proc.wait();
  }

Controlling the GPIOs works fine (on/off). "open" is also recognized correctly and IFTTT sends the right "answer". Just the run() functions only works for shutdown/restart so far.

It'd be awsome to be able to run any script and finally have the unltimate control what GA does for me.
Any ideas?

Best regards,
Flo

1 Like

Maybe i should open another issue on the matter but here is a question i have:

I use the Process::run() command to start a python script which will be running “forever” and communicate with the particle firmware through named pipes. I want to be able to read the Output of the process started with Process:run but it looks like i don’t get the output unless i wait for the process to finish. Is there a way to retrieve the output of the started process while it is actively running ?

Thanks,

Did you ever find a solution? I am running Particle on an old RasPi model 1 B+ and I want to remotely start a python script through IFTTT. I began by using Process proc = Process::run(“sudo python flag.py”); then attempted …run("/home/pi/python.py"); and everything between the two. Nothing worked. I then moved on to writing a shell file that would start the python file for me. When I run it through an ssh, it starts the python program fine, then prints a string. However, when I have particle try to run it, particle will spit out my string, but refuses to start my python script. SOS

[Solved] It turned out that I had two problems. The first was that I needed to make the python script executable. I tried this early on, but it didn’t work because of my second problem: I didn’t have permission to access/edit the database that was storing my variables [note my attempts to include sudo in the Process::run() above]. I gave my script permissions, and it works like a charm now.