Running cat /proc/cpuinfo in glib - c

I've been trying to look for questions on how to use g_spawn_sync() and they said that it is good to use when you want to execute a command in the terminal besides using pipes.
The only thing I can't figure out now is why the command cat /proc/cpuinfo doesn't work. error->message returns (No such file or directory)but if I use commands like ls or cat alone, it works. I also tried running cd /proc && cat cpuinfo but it gives me the same error.
I'm not an expert of glib but I read in the manual that I can use G_SPAWN_SEARCH_PATH so that it will check my PATH for the commands I can use without including the absolute path for the command.
I have the following code:
gchar *argv[] = { "cat /proc/cpuinfo", NULL };
char *output = NULL; // will contain command output
GError *error = NULL;
int exit_status = 0;
if (!g_spawn_sync(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL,
&output, NULL, &exit_status, &error))
{
printf("[getHardwareInfo] DEBUG: Error on g_spawn_sync %s.\n", error->message);
}

tl;dr: Do not use g_spawn_command_line_sync() unless you really know what you are doing.
Firstly, the actual problem you are hitting: John Szakmeister’s comment was correct: g_spawn_sync() takes an array of arguments, the first one of which is the path to the program to execute (or to look for in $PATH, if you’ve specified G_SPAWN_SEARCH_PATH). By passing the array { "cat /proc/cpuinfo", NULL }, you are saying that you want to run the program cat /proc/cpuinfo with no arguments, not the program cat with the argument /proc/cpuinfo.
However, there are many other problems here, and I think it’s important to mention them before people start cargo-culting this code, because they have security implications:
As LegalProgrammer says, why are you spawning cat when you could just call g_file_get_contents()?
Failing that, use GSubprocess instead of g_spawn_*(). It’s a more modern API, which allows you to monitor the lifecycle of the spawned process more easily, as well as getting streaming I/O in and out of the subprocess.
Do not ignore the warnings in the manual about the security implications of using g_spawn_command_line_sync(). There are several:
It will run the first matching program found in your $PATH, so if an attacker has control of your $PATH, or write access to any directory in that $PATH (such as ~/.local/bin), you will end up running an attacker-controlled program.
It’s a synchronous function, so will block on the subprocess completing, which could take unbounded time. Your program will be unresponsive for that time.
It returns the output in a single allocation, rather than as a stream, so if the subprocess returns many megabytes of output, you may hit allocation failures and abort.
The obvious next step from “g_spawn_command_line_sync() seems to do what I want” is “let’s use g_strdup_printf() to put together a command to run with it”, and then you have shell injection vulnerabilities, where an attacker who controls any of the parameters to that printf() can twist the entire shell command to execute their arbitrary code.

I'm answering my question here. After reading the manual again, I decided to use another function, g_spawn_command_line_sync, which is simpler to use than g_spawn_sync.
A simple version of g_spawn_sync() with little-used parameters removed, taking a command line instead of an argument vector. See g_spawn_sync() for full details. command_line will be parsed by g_shell_parse_argv(). Unlike g_spawn_sync(), the G_SPAWN_SEARCH_PATH flag is enabled. Note that G_SPAWN_SEARCH_PATH can have security implications, so consider using g_spawn_sync() directly if appropriate. Possible errors are those from g_spawn_sync() and those from g_shell_parse_argv().
Here is my new code:
char *output = NULL; // will contain command output
GError *error = NULL;
gint exit_status = 0;
if (!g_spawn_command_line_sync("cat /proc/cpuinfo", &output, NULL, &exit_status, &error))
{
printf("[getHardwareInfo] DEBUG: Error on g_spawn_command_line_sync %s.\n", error->message);

Related

Is there a system call to run systemctl in C program (not the system() function)?

I am on a Ubuntu 22.04 server. I want to run:
systemctl restart someService
but want to do so in a C program.
Intuitively I tried:
system("systemctl restart someService")
This did not work even if my program itself has setUid set to root as systemctl does not itself have setUid bit set to root.
I would like to write a program and set its uid to root so that anyone can execute it to restart a certain system service. This is only possible by using some direct function and not the system call as done above. Any suggestions?
I don't think there is a system-call that can do the job of systemctl in general. I think your approach of calling the systemctl command from your program is correct. But, I am not getting into the security considerations here. You should be really careful when writing set-uid programs.
Now, the main issue with your code is that system should not be used from set-uid binaries because it doesn't let you control the environment variables, which can be set maliciously before calling your program to change the behavior of the called process. Besides that, the system command calls /bin/sh to run your command which on some versions of Linux drop privilege as mentioned on the man-page linked above. The right approach would be to use execve family of functions that offer more control and do not spawn a shell. What you need to do can be done in the following way -
int main(int argc, char* argv[]) {
setuid(0);
setgid(0);
char *newargv[] = {"/usr/bin/systemctl", "restart", "someService", NULL};
char *newenviron[] = { NULL };
execve(newargv[0], newargv, newenviron);
perror("execve"); /* execve() returns only on error */
exit(EXIT_FAILURE);
}
Notice the empty (or pure) environment above. It is worth noting that the execve should not return unless there is an error. If you need to wait for the return value from the systemctl command, you might have to combine this with fork

posix_spawn pipe dmesg to python script

I've got several USB to 422 adapters in my test system. I've used FTProg to give each adapter a specific name: Sensor1, Sensor2, etc. They will all be plugged in at power on. I don't want to hard code each adapter to a specific ttyUSBx. I want the drivers to figure out which tty it needs to use. I'm developing in C for a linux system. My first thought was to something like this in my startup code.
system("dmesg | find_usb.py");
The python script would find the devices since each one has a unique Product Description. Then using the usb tree to associate each device with its ttyUSBx. The script would then create /tmp/USBDevs which would just be a simple device:tty pairing that would be easy for the C code to search.
I've been told...DoN't UsE sYsTeM...use posix_spawn(). But I'm having problems getting the output of dmesg piped to my python script. This isn't working
char *my_args[] = {"dmesg", "|", "find_usb.py", NULL};
pid_t pid;
int status;
status = posix_spawn(&pid, "/bin/dmesg", NULL, NULL, my_args, NULL);
if(status == 0){
if(waitpid(pid, &status, 0) != -1);{
printf("posix_spawn exited: %i", status);
}
I've been trying to figure out how to do this with posix_spawn_file_actions(), but I'm not allowed to hit the peak of the 'Ballmer Curve' at work.
Thanks in advance
Instead of using /dev/ttyUSB* devices, write udev rules to generate named symlinks to the devices. For a brief how-to, see here. Basically, you'll have an udev rule for each device, ending with say SYMLINK+=Sensor-name, and in your program, use /dev/Sensor-name for each sensor. (I do recommend using Sensor- prefix, noting the initial Capital letter, as all device names are currently lowercase. This avoids any clashes with existing devices.)
These symlinks will then only exist when the matching device is plugged in, and will point to the correct device (/dev/ttyUSB* in this case). When the device is removed, udev automagically deletes the symlink also. Just make sure your udev rule identifies the device precisely (not just vendor:device, but serial number also). I'd expect the rule to look something like
SUBSYSTEM=="tty", ATTRS{idVendor}=="VVVV", ATTRS{idProduct}=="PPPP", ATTRS{serial}=="SSSSSSSS", SYMLINK+="Sensor-name"
where VVVV is the USB Vendor ID (four hexadecimal digits), PPPP is the USB Product ID (four hexadecimal digits), and SSSSSSSS is the serial number string. You can see these values using e.g. udevadm info -a -n /dev/ttyUSB* when the device is plugged in.
If you still insist on parsing dmesg output, using your own script is a good idea.
You could use FILE *handle = popen("dmesg | find_usb.py", "r"); and read from handle like it was a file. When complete, close the handle using int exitstatus = pclose(handle);. See man popen and man pclose for the details, and man 2 wait for the WIFEXITED(), WEXITSTATUS(), WIFSIGNALED(), WTERMSIG() macros you'll need to use to examine exitstatus (although in your case, I suppose you can just ignore any errors).
If you do want to use posix_spawn() (or roughly equivalently, fork() and execvp()), you'd need to set up at least one pipe (to read the output of the spawned command) – two if you spawn/fork+exec both dmesg and your Python script –, and that gets a bit more complicated. See man pipe for details on that. Personally, I would rewrite the Python script so that it executes dmesg itself internally, and only outputs the device name(s). With posix_spawn(), you'd init a posix_file_actions_t, with three actions: _adddup2() to duplicate the write end of the pipe to STDOUT_FILENO, and two _addclose()s to close both ends of the pipe. However, I myself prefer to use fork() and exec() instead, somewhat similar to the example by Glärbo in this answer.

Launch interactive session via script bridge

I am trying to launch an interactive debugging session from a python script via the SWIG-generated lldb module. The program to debug is nothing but an empty main function. Here is my current attempt:
import lldb
import sys
import os
debugger = lldb.SBDebugger.Create()
debugger.SetAsync(False)
target = debugger.CreateTargetWithFileAndArch("a.out", "")
# The breakpoint itself works fine:
fileSpec = lldb.SBFileSpecList()
mainBp = target.BreakpointCreateByName("main", 4, fileSpec, fileSpec)
mainBp.SetAutoContinue(False)
# Use the current terminal for IO
stdout = os.ttyname(sys.stdout.fileno())
stdin = os.ttyname(sys.stdin.fileno())
stderr = os.ttyname(sys.stderr.fileno())
flag = lldb.eLaunchFlagNone
target.Launch(target.GetDebugger().GetListener(), [], [], stdin, stdout,
stderr, os.getcwd(), flag, False, lldb.SBError())
It seems to me that whatever flag I pass to target.Launch (I tried amongst those flags), there is no way of switching to an interactive editline session. I do understand that the primary purpose of the python bindings is non-interactive scripting, but I am nevertheless curious whether this scenario could be made possible.
There is a method on SBDebugger to do this (RunCommandInterpreter). That's how Xcode & similar make lldb console windows. But so far it's only been used from C and there's something wrong with the C++ -> Python bindings for this function such that when you try to call it from Python you get a weird error about the 5th argument being of the wrong type. The argument is an int& and that gives SWIG (the interface generator) errors at runtime.
Of course, you could just start reading from STDIN after launch and every time you get a complete line pass it to "SBCommandInterpreter::HandleCommand". But getting RunCommandInterpreter working is the preferable solution.

Why does posix_spawn() fail where popen() works?

I'm successfully using popen() to run commands from within my C program. As I understand, it uses fork() and exec() (or variants of those) behind the curtains. This works very well:
FILE *fd = popen("xterm", "r");
pclose(fd);
... will bring up a new xterm window, as expected.
Now I'm trying to achieve the same with posix_spawn(), which I understand to be possibly more resource-friendly, especially if we don't plan on communicating with the new child process:
/* p.we_wordv contains the argv, index 0 holds the actual command */
pid_t pid;
posix_spawnp(&pid, p.we_wordv[0], NULL, NULL, p.we_wordv, NULL);
... but this, for xterm as the command, yields the following on the parent's output:
xterm: Xt error: Can't open display:
xterm: DISPLAY is not set
Trying to launch other processes will yield other error messages, fail silently, or, in some cases like ls, work as expected. This makes it a bit hard for me to actually see a pattern yet.
Can you point out what is causing the second approach to behave differently than the first?
The message DISPLAY is not set tells you that xterm didn't find the DISPLAY environment variable. All graphical-output programs use this environment variable to connect to your screen.
It didn't find the variable because the environment was empty (it's the last NULL in your posix_spawnp function call). It seems that popen reuses the environment of current process, so it doesn't have this problem.
You might want to pass a manually-created environment, containing only the needed stuff, or just pass whatever environment your process has. The latter is more flexible (xterm will inherit various configuration settings from your process, which inherits them from your shell) but may be a security risk.
To access the environment of your process, use the environ global variable or change your main function to receive an additional parameter:
int main(int argc, char *argv[], char *envp[])
{
...
posix_spawnp(&pid, p.we_wordv[0], NULL, NULL, p.we_wordv, envp);
}

What should COMSPEC and PATH environmental variables be to locate the command-interpreter, using system()?

Are the OS (XP) environmental variables the same used in a process running from visual studio .NET C++?
It seems the command interpreter is not found:
When using NULL as the command, system() returns 0 and with command - ENOENT Command interpreter cannot be found.
In windows (System->Environmental Variables), COMSPEC contains the path to cmd.exe
PATH does not.
What should PATH be?
Other than this, not sure why it can not find the interpreter.
Any suggestions are appreciated. Thanks.
if( system("tail -500 log.txt") == -1)
{
//Error calling tail.exe on log
//errno is a system macro that expands int returning
//the last error. strerror() converts the error to it's
//corresponding error message.
printf("Error calling tail.exe with system(): %s",strerror( errno ));
}
EDIT1
Stepping into system() argv[0] = _tgetenv(_T("COMSPEC"));returns a bad pointer. Being this is a cgi executable, the COMPSEC is not properly set or inherited from the OS.
I now set COMSPEC before the process is started and use CreateProcess() as in example 2
However, create process still returning 0? Getting closer. See any issues with this? Thanks.
if (! SetEnvironmentVariable("COMSPEC", "C:\\WINDOWS\\system32\\cmd.exe") )
{
printf("SetEnvironmentVariable failed (%d)\n", GetLastError());
}
//r = system("dir c:\\");
r = CreateProcess("dir.exe", NULL, NULL, NULL, TRUE, NULL,
NULL, // inherit parent's environment
NULL, &si, &pi);
EDIT 2
SetEnvironmentVariable() did not work. However, putenv does.
_putenv( "COMSPEC=C:\\WINDOWS\\system32\\cmd.exe" ); // C4996
Not sure what the difference is...?
Now that this env var is set, any request on the cgi app from the browser gives the option to save the cgi.exe instead of executing it.. Not sure why this has changed based on this env var?
The environment variables are inherited when running a process, including system(...) call. Unless there is something weird going on, usually running %windir%\system32\cmd.exe should do the trick, it should expand the environment variable, unless you can use the API to get the windows directory 'GetWindowsDirectory'. See here for an example from the MSDN.
Edit: IIRC, COMSPEC environment variable, if done on the command line
> echo %COMSPEC%
C:\WINDOWS\system32\cmd.exe
You got a bad pointer, because it is not probably set up, the above echo command should prove that, if you get no output, it is not set, right click on 'My Computer', left-click on 'Properties', a dialog with tab-pages appear, click on 'Advanced', look for 'Environment Variables'...see the two screenshots here...
Also I should point out that you are setting the environment variable temporarily, hence it will not see the 'COMSPEC'....it is not permanent, the only permanent way to do it is follow the screenshots...
I am trying to get the screenshots in place....
Edit#2:
Just to point out this, when you set the Environment variable here, that is temporary - not permanent!
if (! SetEnvironmentVariable("COMSPEC", "C:\\WINDOWS\\system32\\cmd.exe") )
{
printf("SetEnvironmentVariable failed (%d)\n", GetLastError());
}
//r = system("dir c:\\");
r = CreateProcess("dir.exe", NULL, NULL, NULL, TRUE, NULL,
NULL, // inherit parent's environment
NULL, &si, &pi);
When using the call CreateProcess, it is bound to fail, look at the comment "inherit parent's environment", that cannot happen as the environment was set up temporarily. Have you tested the simple echo command here. Something is wrong as to why the COMSPEC variable is not set..after setting it permanently - you will need to reboot the machine for it to work. Then the echo command above should show the value for that environment variable, and in turn, this
argv[0] = strdup(_tgetenv(_T("COMSPEC")));
should return a proper pointer...by the way, I think that should be strdup'd also...
Edit#3: Whoops I noticed when I had '&pi' used, it came up as a pi symbol instead!...duh, that's amended now...also I have amended this 'argv' code here:
argv[0] = _tcsdup(_tgetenv(_T("COMSPEC")));
Start + Control Panel, System, Advanced, Environment variables. Select Path in the System variables section, Edit. At the very least it should look like this:
%SystemRoot%\system32;%SystemRoot%;%SystemRoot%\System32\Wbem
Ask more questions about this at superuser.com

Resources