I am developing a shared-library L which is used by an other system service S. In my program I need to call a small program P, which uses Oracle shared libraries.
The small program P is packaged and installed correctly, and the environment variables, such as PATH,LD_LIBRARY_PATH and ORACLE_HOME are set correctly. I can run P on command line without any problem.
But when service S call my library L which runs the small program P via system(), it gives me a return code 127. I've googled, people says it's a command not found error, probably a PATH issue, so I've tried with absolute path like the following
int status = system("/usr/bin/myprog --args");
if (status < 0) { ... }
ret = WEXITSTATUS(status);
ret still equals 127.
Any idea please ? Thank you.
Update
It turns out that the service S is launched via command daemon, in its init.d script, I have found the following line:
daemon /usr/bin/myserv
if I export explicitly all my environment variables (PATH, ORACLE_HOME and LD_LIBRARY_PATH), it works. I don't know if daemon eliminates my environment variables.
this excerpt from the man page for system()
-----------------------------------------------------------------
The value returned is -1 on error (e.g., fork(2) failed), and the
return status of the command otherwise.
This latter return status is
in the format specified in wait(2).
Thus, the exit code of the command
will be WEXITSTATUS(status).
In case /bin/sh could not be executed,
the exit status will be that of a command that does exit(127)."
-----------------------------------------------------------------
indicates the 127 means that /bin/sh could not be executed.
Well, I have found the answer:How to make unix service see environment variables?,the environment variables are removed in init.d script.
Related
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
I take LTP (Linux Test Project) on embedded device. Device stuck in following while loop in test case setfsgid03, because getgrgid() always return NULL when it is called by nobody .
It works fine when it is called by root on embedded device. And it works fine on x86 linux host when it is called by nobody.
Is it caused by any configuration of linux on device?
Relevant code snippet is below:
gid = 1;
while (!getgrgid(gid))
gid++;
getgrgid will read the entries from /etc/group or with Glibc more generally from sources specified in /etc/nsswitch.conf. If /etc/group does not exist or it doesn't have other groups besides the gid then this code will loop at least until the wrap-around/signed overflow of gid. If there is only the entry for nobody at pid -2 it will also take ages to find that pid.
All in all, the code is utterly bad. I'd just ensure that there is an entry in /etc/group with GID 2 say; the proper way to find a defined non-root gid would be to use getgrent_r successively until the returned record has gr_gid != 0, and fail if NULL is returned before such a record is found.
I have a complex cgi executable written in C, I configured in Apache2 and now it is running succesfully. How can I debug this program in the source code, such as set break points and inspect variables? Any tools like gdb or eclipse? Any tutorial of how to set up the debugging environment?
Thanks in advance!!
The CGI interface basically consists in passing the HTTP request to the executable's standard input and getting the response on the standard output. Therefore you can write test requests to files and manually execute your CGI without having to use Apache. The debugging can then be done with GDB :
gdb ./my_cgi
>> break some_func
>> run < my_req.txt
with my_req.txt containing the full request:
GET /some/func HTTP/1.0
Host: myhost
If you absolutely need the CGI to be run by Apache it may become tricky to attach GDB to the right process. You can for example configure Apache to have only one worker process, attach to it with gdb -p and use set follow-fork-mode child to make sure it switches to the CGI process when a request arrives.
I did this: in cgi main i added code to look for an existing file, like /var/tmp/flag. While existing, i run in a loop. Time enough to attach to cgi process via gdb. After then i delete /var/tmp/flag and from now I can debug my cgi code.
bool file_exists(const char *filename)
{
ifstream ifile(filename);
return ifile;
}
int cgiMain()
{
while (file_exists ("/var/tmp/flag"))
sleep (1);
...
your code
Unless FastCGI or SCGI is used, the CGI process is short-lived and you need to delay its exit to have enough time to attach the debugger while the process is still running. For casual debugging the easiest option is to simply use sleep() in an endless loop at the breakpoint location and exit the loop with the debugger once it is attached to the program.
Here's a small example CGI program:
#include <stdio.h>
#include <unistd.h>
void wait_for_gdb_to_attach() {
int is_waiting = 1;
while (is_waiting) {
sleep(1); // sleep for 1 second
}
}
int main(void) {
wait_for_gdb_to_attach();
printf("Content-Type: text/plain;charset=us-ascii\n\n");
printf("Hello!");
return 0;
}
Suppose it is compiled into cgi-debugging-example, this is how you would attach the debugger once the application enters the endless loop:
sudo cgdb cgi-debugging-example $(pgrep cgi-debugging)
Next you need to exit the infinite loop and wait_for_gdb_to_attach() function to reach the "breakpoint" in your application. The trick here is to step out of sleep functions until you reach wait_for_gdb_to_attach() and set the value of the variable is_waiting with the debugger so that while (is_waiting) exits:
(gdb) finish
Run till exit from 0x8a0920 __nanosleep_nocancel () at syscall-template.S:81
0x8a07d4 in __sleep (seconds=0) at sleep.c:137
(gdb) finish
Run till exit from 0x8a07d4 in __sleep (seconds=0) at sleep.c:137
wait_for_gdb_to_attach () at cgi-debugging-example.c:6
Value returned is $1 = 0
(gdb) set is_waiting = 0 # <<<<<< to exit while
(gdb) finish
Run till exit from wait_for_gdb_to_attach () cgi-debugging-example.c:6
main () at cgi-debugging-example.c:13
Once you are out of wait_for_gdb_to_attach(), you can continue debugging the program or let it run to completion.
Full example with detailed instructions here.
I'm not sure how to use gdb or other frontends in eclipse, but I just debugged my CGI program with gdb. I'd like to share something that other answers didn't mention, that CGIs usually need to read request meta-variables defined in RFC 3875#4.1 with getenv(3). Popular request variables in my mind are:
SCRIPT_NAME
QUERY_STRING
CONTENT_LENGTH
CONTENT_TYPE
REMOTE_ADDR
There variables are provided by http servers such as Apache. When debugging with gdb, we need to set these values by our own with set environment. In my case, there're only a few variables neededa(and the source code is very old, it still uses SCRIPT_URL instead of SCRIPT_NAME), so here's my example:
gdb cgi_name
set environment SCRIPT_URL /path/to/sub/cgis
set environment QUERY_STRING p1=v1&p2=v2
break foo.c:42
run
For me both solutions for debugging the CGI in gdb without web server presented above didn't work.
Maybe the second solution works for a GET Request.
I needed a combination of both, first setting the environment variables from rfc3875 (not sure if all of them are really neded).
Then I was able to pass only the params (not the compltete request) via STDIN from a file.
gdb cgi_name
set environment REQUEST_METHOD=POST
set environment CONTENT_LENGTH=1337
set environment CONTENT_TYPE=application/json
set environment SCRIPT_NAME=my.cgi
set environment REMOTE_ADDR=127.0.0.1
run < ./params.txt
With params.txt:
{"user":"admin","pass":"admin"}
I am using system function in C code to invoke cp command.
I want to know whether it got executed successfully or not?
Use system() function. It returns the status of command executed. If this is 0, then it would seem this is successfull.
Should be enought to check the return value of system call.
From man system on Mac:
The system() function returns the exit
status of the shell as returned by
waitpid(2), or -1 if an error occurred
when invoking fork(2) or waitpid(2). A return value of 127
means the execution of the shell
failed.
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