Linux versus Particle-agent


#1

Mod Edit (@harrisonhjones): Formatted his blog post here. Deleting all related posts

When I installed particle-agent on 2 of my RPis I didn’t realize that they would run in the background as “root” (superuser). I suppose the rationale is so an Arduino-like sketch program could access the GPIO pins. However, there is a better way, google “raspberry pi /dev/gpiomem”.

I had no problem installing their particle-agent package. I did on 2 RPis: a Bv2 (40 pins) and a Bv3 (quad processor). I thought I could just use gcc to compile, but how to get to Particle libraries. Turns out you can just use the “Web IDE” – like for a Particle Photon. Once you install and run particle-agent your RPi devices will show up on the web page like other devices. Anyway, I then wrote the following simple program:

#include <iostream>
#include <fstream>
using namespace std;

char Rstr[100];
int Ct = 0;

void setup() {
    Particle.variable("dhpi1v", Rstr, STRING);
    Particle.function("dhpi1f", pifunc);
}

void loop() {
}

int pifunc(String cmd) {
    int arg = cmd.toInt();
    ++Ct;
    sprintf(Rstr, "Call count = %d, Arg = %d\n", Ct, arg);
    ofstream pifile;
    pifile.open ("/home/pi/pi_out.txt"); // belongs to "root"
    pifile << Rstr;
    pifile.close();
    return Ct;
}

Note the fully qualified path for the output file. Particle-agent doesn’t know about your home directory. Here’s my shell script to get results (from my Mac Terminal app):

id=???
ac=???
var=dhpi1v
fun=dhpi1f
arg=123
echo Call Pi function:
curl https://api.particle.io/v1/devices/$id/$fun -d access_token=$ac -d args=$arg
echo
echo Get Pi variable:
curl  https://api.particle.io/v1/devices/$id/$var?access_token=$ac
echo

Shell results:

$ sh pitest
Call Pi function:
{
  "id": "???",
  "last_app": "",
  "connected": true,
  "return_value": 6
}
Get Pi variable:
{
  "cmd": "VarReturn",
  "name": "dhpi1v",
  "error": null,
  "result": "Call count = 6, Arg = 123",
  "coreInfo": {
    "last_app": "",
    "last_heard": "2016-12-13T16:35:58.241Z",
    "connected": true,
    "last_handshake_at": "2016-12-13T16:03:52.588Z",
    "deviceID": "???",
    "product_id": 31
  }
}

Note: I don’t think much of Particle’s inconsistently formatted output. And yipe! As I mentioned above, the file I wrote should not belong to root.

$ ls -l:

-rw-r--r--  1 root root     26 Dec 13 17:08 pi_out.txt

So I added the following to my sketch:

#include <sys/types.h>
#include <unistd.h>

#define SAFE_UID 1000 // i.e., the "pi" user built into RPi Linux
. . .
int Euid;
int SetRet;

void setup() {
    SetRet = setuid(SAFE_UID); // don't be ROOT!
    Euid = geteuid();
. . .
// in pifunc

sprintf(Rstr, "SetRet = %d, Euid = %d, Call count = %d, Arg = %d\n", SetRet, Euid, Ct, arg);

Anyway, you’d think that setuid() would have fixed the permissions problem. But no such luck. Still belongs to root.

Two other problems (yes, I know the “Particle Pi” is beta):

Issue 1: When I changed the source program (as above), downloaded it and ran it the output message didn’t change. After much useless retrying I went looking for the files the Particle installed (good old Unix/Linux, everything is a file). So I found a directory whose name is my Pi’s ID# and that contains a file named firmware.bin.

/var/lib/particle/devices/7ab...MyID/firmware.bin

And the directory permissions were rwx------.

So, apparently, the downloader can install a firmware.bin but can’t over-write it afterwards. So I unilaterally changed them to rwxrwxrwx (probably not optimal). Now I could recompile.

Issue 2: While flailing about with the above problem I decided to download the sample program on my RPi Brev3. I got the same results except the up-to-date output was displayed. But then it got interesting. This RPi is 50 miles away at the moment. I use it to monitor Particle Photons installed at my granddaughter’s farm. It runs off UPS and hasn’t been down in months. But I try to be careful so I have a crontab task that checks the RPi’s on-chip temperature sensor every few minutes. The sustem generally runs at about 110F. If it gets to 130F the RPi sends me an email warning. And at 140F it executes shutdown. Guess what? 20 minutes after running my test program I got the “RPi HOT!” email and it has been down ever since. I can’t blame particle-agent just yet, but one thing I know: CPU load raises the sensor’s temperature. I have a favorite test program that I have used for 30 years. It is now in Python and it computes PI to as many places as you specify. So PI to 10,000 places raises the temp a few degrees. That only uses a single processor (probably). Anyway, this weekend I will visit this RPi and find out.


#4

Mod Edit (@harrisonhjones): Removed reference to formatting/offsite links

@rch, It would be great to summarize your issue or questions so the thread can continue here for other folks. I do know that security has been discussed in another topic:

:smile:


#5

Thanks for posting such a detailed message to the community!

We’re definitely open to suggestions on how to run the firmware as a less privileged user while keeping the ability to interact with hardware.

Regarding issue 1, the device directory has permission rwx------ so that regular users can’t access the private key or other private files. Flashing new firmware over the air (through the Particle CLI or the Web IDE) should create output.bin in the device directory, cause the firmware to exit, the agent to move output.bin to firmware.bin and relaunch with the new firmware. You should be able to tell the date when firmware.bin was created with ls -l.

Regarding issue 2, this is a tricky question. Right now the loop() function runs as often as possible so you can control hardware IO at full speed. This uses up 100% of one core. In case you don’t have any processing to do in loop() you can add a delay(10); in there to sleep for 10 ms in between loop iterations. This will bring the CPU usage down to ~1%. There’s no good way of guessing if the programmer wants the firmware to run as fast as possible or use as little resources as possible.


#6

Re jvanier post:

  1. It’s counter intuitive that the empty loop() is a cpu hog. I will definitely add the delay(). But you should have mentioned it in bold type. After all, the point of an RPi is that other things are going to be running.

  2. The particle-agent processes should not be running as root. Well, maybe exec some setuid bit program momentarily. Part of the reason for Particle cloud on the RPi is to do things like file access. Somehow the user’s sketch should run as his own user ID. One should be able to declare the home directory and default permissions mask. As it is now, I can get the file where I want by full path spec but I can’t seem to get the file ownership to be anything but root. setuid() didn’t work and neither chown() after I wrote the file (although both returned “0”. Not acceptable. BTW: all RPis come with a user named “pi” at user # 1000. That could be the default for the beta.

  3. NEW: I guess we can only run 1 (“ONE”) particle program at a time?

So, can I update a sketch now without device ID directory?


#7

Re the simple Particle-RPi test program: here’s my latest try (eliminating most C++ crap).

#include 
#include 

#define SAFE_UID 1000 // i.e., the "pi" user built into RPi Linux
#define MY_PATH "/home/pi"
char Rstr[100];
int Ct = 0;

void setup() {
    Particle.variable("dhpi1v", Rstr, STRING);
    Particle.function("dhpi1f", pifunc);
}

void loop() {
    delay(10); // kludge to keep the empty loop from being a cpu hog
}

int pifunc(String cmd) {
    int arg = cmd.toInt();
    char file[100];
    FILE *fp;
    
    ++Ct;
    sprintf(Rstr, "Call count = %d, Arg = %d\n", Ct, arg);
    sprintf(file, "%s/pi_out.txt", MY_PATH);
    fp = fopen(file, "w");
    fputs(Rstr, fp);
    fclose(fp);
    chown(file, SAFE_UID, SAFE_UID); // Works.
    return Ct;
}

#8

Replying to my own and jvanier posts: I rebooted my overheated RPi Bv3. I.e., still with an empty loop() function. CPU time (from ps) basically same as elapsed clock time. I tried to load the script just above (with delay(10) in loop()). Well, gthat decreases CPU usage but not to 1%. I get 7 seconds per minute: 12%-ish. Also, again I could not replace the program without making the device ID directory writable. In case there is interest, here is my shell script that monitors CPU temperature.

This file started at system boot by this line in crontab:
@reboot nohup sh bin/cputemp.sh

while true ;
do
 a=`/opt/vc/bin/vcgencmd measure_temp` # returns Celsius
 b=`expr "$a" : '.*=\([0-9][0-9]*\)'`
 if [ $b -gt "70" ] ; # 160F
 then
  echo 'CPU HOT!' | mail -s 'DicksRpi4-HOT' myemail@gmail.com
  sudo halt
 fi
 if [ $b -lt "55" ] ; # 130F
 then
  sleep 300 # sleep longer if under 130F
 else
  sleep 60
 fi
done

#9

Again adding to (or correcting) previous posts: Here’s an even better version of my RPi test sketch:

#include <stdio.h>
#include <unistd.h>

#define SAFE_UID 1000 // i.e., the "pi" user built into RPi Linux
#define MY_PATH "/home/pi"
char Rstr[100];
int Ct = 0;

void setup() {
    int rvalue = setuid(SAFE_UID); // don't be ROOT!
    Particle.variable("dhpi1v", Rstr, STRING);
    Particle.function("dhpi1f", pifunc);
}

void loop() {
    delay(25); // kludge to keep the empty loop from being a cpu hog
}

int pifunc(String cmd) {
    int arg = cmd.toInt();
    char file[100];
    FILE *fp;
    
    ++Ct;
    sprintf(Rstr, "Call count = %d, Arg = %d\n", Ct, arg);
    sprintf(file, "%s/pi_out.txt", MY_PATH);
    fp = fopen(file, "w"); // file will belong to "pi"
    fputs(Rstr, fp);
    fclose(fp);
    return Ct;
}

The setuid() system call should be the 1st line in setup()!


#11

If you want to protect the “private key” you should not execute …devices/ID/firmware.bin directly. This lets the ID code be displayed by “ps ax”. This is easily avoided.


#12

For Particle:
I wrote a pair of minimal programs – one Photon, one RPi that swapped numbered publish/subscribe messages once per minute. This ran fine yesterday – nearly 500 minutes with out a missed message. But today I thought to try to test how long the RPi publish to Photon subscribe and back took. So I added this to the RPi code:

#include 
...
time_t Start, Elapsed;
... 
Start = time(NULL);
...
Elapsed = time(NULL) - Start; // to be appended to a file

And the program stopped working (no error message). When I commented those lines out it worked again. I can see the difficulty but there needs to be a warning.


#13

Sorry the above is wrong. It worked when I fixed a simple error in sprintf format. The turn-around time for the double publish/subscribe seems to be 2 seconds.


#14

Hey there @rch – just wanted to note that we’re currently on holiday break here at Particle, but will be back in a few days to catch up on everything you’ve been hacking away on over the last couple of weeks.

Thanks for continuing to post, and sorry for the radio silence! We’ll be with you shortly :slight_smile:


#15

Not too “shortly”, I guess.


#16

Hey @rch – the last message you posted seemed to indicate that the problem you were seeing had been resolved.

We’re back on holiday so able to respond with normal turnaround times. Is there still an issue that we’re seeing that we can help to reproduce and log?

Reading back through the thread, it seems like you have a couple of pieces of feedback that you’d like to see improved in future iterations of the beta:

  • Default behavior not to use 100% core resources for “full-speed” execution
  • particle-agent running with limited scope permissions

Is that right?


#17

Also, as a quick plug, we’re always happy to accept issues and suggestions for ways to improve the particle-agent in our open-source repo, here.

We’re also open and welcome to PRs!


#18

Among my concerns:

  1. Agent running as root. Also see “FEEDBACK: Raspberry Pi particle-agent structure -- complaint”. The Arduino “sketch” model works for Photon but not a Linux-based system. As I mentioned in some now-lost message, all I really want from Particle on an RPi is access to Particle.function, -variable, -publish, and -subscribe. There are already (better) Linux tools for the rest.
  2. Possibility of overheating a RPi quad processor: My system is installed in a plastic enclosure with no heat sinks. But it ran for months before my Particle process (empty loop()) overheated it in 30 minutes. I see no reason to believe this is an isolated case.
  3. The way agent-service runs on Linux exposes my device ID:
    $ ps ax | grep particle

    1206 ? S 37:07 /var/lib/particle/devices/…dev-id…/firmware.bin -v 70

etc.


#19

Regarding #1 (root) - Obviously that’s not ideal. Not sure where we are on running as a different user. I personally don’t quite understand why it’s such a large issue, even after reading your posts, but I still agree that we should give users the options to run as a different user. You seem to change the user by using the setuid(SAFE_UID); command. Why not add that to STARTUP()?

Regarding #1 (arduino) - Not sure why the sketch model doesn’t work for you. You can use it to expose the Particle Cloud functions as needed. Others might want to do hardware-related stuff (I being one of the them). Why not have both?

Regarding #2 - I believe Julien gave you a solution for the CPU issue with delay(). Perhaps it would be a good idea to automatically add a delay() by default on the Pi firmware and then let users disable it. That might also just confuse users.

Regarding #3 - Device Ids aren’t actually “secret”. They are kinda like MAC addresses.


#20

I’d never noticed the STARTUP() feature. Obviously, the thing to do is start each RPi sketch with–

#define SAFE_UID 1000 // i.e., the "pi" user built into RPi Linux
#define MY_PATH "/home/pi"

void uid_fix() {
    setuid(SAFE_UID); // don't be ROOT!
    chdir(MY_PATH);
}

STARTUP( uid_fix() );. . .

When the usual Arduino/Particle programmers move to Linux they can do great damage to their systems as superuser. This includes removing the filesystem. My experience with the hardware-hackers that have migrated
to RPi is that they are often OS-challenged. I’ve covered this at length at “http://dicks-raspberry-pi.blogspot.com/”. (I have the opposite problem).
I think the above uid_fix should be the default.


#21

Thank you for the feedback, @rch. As we make plans for development to transition the Raspberry Pi software out of beta, we’ll certainly be taking your suggestions into account.

In the meantime, if there’s something you’re having trouble or have questions in development that you can’t solve with a workaround, please let us know, and we’d be happy to chime in with answers and suggestions.