What does "/bin/sh" stand for in execlp command? - c

I want to execute a shell command with execlp, I tried with the following instruction :
execlp("sh", "sh", "-c", p_command, (char*)NULL);
p_command is a pointer to a const char representing a shell command line.
My minimal test tells me the program succeded as expected. I first choose to use "/bin/sh" instead of "sh" but I've learned that p(ath) in execlp allows us to avoid writing the full path, as if exec will complete the path for us ; so I removed "/bin/".
My concern is that I never saw a code using execlp with only "sh", as it effectively does for exemple for ls we can directly use "ls" instead of "/bin/ls".
As a beginner I am wondering what "/bin/sh" stands for, what is the difference between "sh" and "/bin/sh" in this situation and why we have to write the full path for execlp to execute a shell ?

When the path passed to execlp is sh, execlp searches for it in the directories listed in the PATH environment variable. If an attacker is able to modify the PATH variable in the environment that runs your program, they can set it to list a directory of their choosing, and they can place their own program named sh in that directory. Then your program will execute their program instead of executing the system sh program. In some cases (depending on a bit in the file’s mode bits), programs are executed with the permissions of their owners rather than the permissions of the user executing the program. Such programs must be written carefully to avoid situations like this, where an attacker would be able to exploit the program.
When the path passed to execlp is /bin/sh, execlp looks for it in the path that is /bin/sh starting from the root of the file system, called /. This will always use the sh program that the system administrator has put in the /bin directory (usually done as part of system installation).

Related

how does shell run sh scripts?

We know that each time a user runs a program by typing the name of an executable object file to the shell, the shell creates(use fork) a new process and then loads(use execve) and runs the executable object file in the context of this new process.
Below is my understanding of how a shell works internally, please correct me if I was wrong:
Commands such as ls, cat etc are executable objects (source file written by C) are in /bin/ directory. for example, when a user type in bash shell ls to list files and directories, the bash shell inteprets ls command and fork a child process to run ls
Q1-Is my understanding correct?
Q2-if my understanding is correct, then when a shell run .sh script file which is:
#!/bin/sh
echo "what is your name?"
read name
so the shell forks two child processes for echo and read, then how does these two processes communicate with each other? I mean how does the return output of echo process get passed to read process?
Is my understanding correct?
Generally, yes. But the executable file not necessarily is in /bin/. A file named ls is searched in paths specified inside PATH environment variable and the match is used. The file can be in /usr/bin /usr/sbin /usr/local/bin etc.
And ls may be a builtin. Or a function. Or an alias.
so the shell forks two child processes for echo and read
And this is where a "built-in" comes in. A built-in is an internal part of the shell handled internally by the shell. There is no fork, just some internal code is run and that way it can modify the environment variables. echo not necessarily is a builtin, it only outputs data. But read has to be handled specially and most probably is a builtin for it to modify name variable (there is no requirement for read to be builtin, it may not be, but usually shell writers solve this problem by just making read a builtin).
On bash you can check the type of command with type. Ex. type echo shows echo is a shell builtin.
I mean how does the return output of echo process get passed to read process?
It doesn't.
You may want to read posix Command Search and Execution.

Is is possible to implement internal shell command (shell builtin) by only using the exec() functions?

I want to implement some Linux commands by only using the exec() family of functions. When I use external commands such as "ls", "whoami", it runs well. However, I can't run internal commands (shell builtins) such as "export". Is there any other way to run those commands? And also, why can system() implement those internal commands even though it uses execl() function?
The Linux implementation of system does indeed use execl, but it uses it to run a shell, not to directly invoke the utility. man system is quite explicit:
The system() library function uses fork(2) to create a child process that executes the shell command specified in command using execl(3) as follows:
execl("/bin/sh", "sh", "-c", command, (char *) 0);
So system runs an instance of the standard shell (/bin/sh), passing it two command-line parameters: -c and the argument to system. Shells are expected to interpret the command-line flag -c as requesting the the shell execute the following argument as a shell command. The shell will certainly be able to execute its own builtins from a -c argument in the same way that it executes them if you type them interactively.
However, although you can execute a cd or export command with system, you will find that it is utterly pointless because it works on the execution environment of the shell invoked by system. That shell will terminate as soon as it finishes executing the command it has been asked to execute, so any changes to its execution environment will immediately vanish.
So that's not very useful if you are trying to write a shell. You will want cd to actually change working directories and export to modify your shell's environment variables. So, just like the system shell, you'll have to implement your own builtins. As the word "builtin" implies, these commands are interpreted directly by the shell itself, rather than being passed to some external utility to implement.
For these particular commands, you'll probably want to investigate the standard library functions setenv(3) (to modify environment variables) and chdir(2) (to change the current working directory).

Need to get C program name inside shell script

I have an occasion where a C program invokes a shell script, which in-turn does some copying stuff from the CD mount location to an installation directory.
Now my question is that, is there a straightforward approach to get the absolute path of this C program inside this shell script ?.
I tried a couple of approaches that includes using "$(ps -o comm= $PPID)" from within the script, but nothing did work out till now. I know that I can create a temporary file from the C program which contains its own name (argv[0]) and then make the shell script to read that file, but I don't want to follow that approach here.
Of course, it can be passed as an argument to the script, but I was thinking why the bash built-in macros or something cannot be used here
On linux there is a /proc/self/exe path that points the absolute path of the current executed file. So you can push an environment variable that contains the path before spawning the shell. Something like:
readlink("/proc/self/exe",...,buf);
putenv("MYEXE",buf);
system("thescript");
and accessing the variable in the script:
echo $MYEXE
Before running a foo command you could use which like
fooprog=$(which foo)
to get the full path of the program (scanning your $PATH). For example which ls could give /bin/ls ....
On Linux specifically you could use proc(5).
In your shell process (running bash or some POSIX compliant shell) started by your C program, $PPID give the parent process id, hopefully the pid of the process running your C program.
Then the executable is /proc/$PPID/exe which is a symbolic link. Try for example the ls -l /proc/$PPID/exe command in some terminal.
(notice that you don't run C source files or stricto sensu C programs, you often run some ELF executable which was built by compiling C code)
You might have weird cases (you'll often ignore them, but you might decide to handle them). Someone might move or replace or remove your executable while it is running. Or the parent process (your executable) died prematurely, so the shell process becomes orphan. Or the executable removed itself.

execve not taking environment parameters

I want to write a program which executes the Linux ls command.
I really only want to type in ls and not /bin/ls, so I want to do this using execve (execvp is not an option).
I tried:
char *env[] = { "SHELL=/bin/bash",
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games",
"_=/usr/bin/env",
(char *)0 };
execve(parmList[0], parmList, env);
But it does not seem to work, ls is not recognized but /bin/ls is. Nothing is wrong with parmList because I tried this with execvp and it worked.
Any ideas?
If parmList[0] contains just ls, execve() won't find the ls command unless it is in the current directory.
With execve(), the value of PATH in the environment is immaterial — execve() does not use it. You'd use execvp() (or execvpe() if it is available) to execute the command on the current process's PATH.

Using the exec() family to run the "cd" command

I know that cd is a shell built-in ,and I can run it by using system().
But is that possible to run the cd command by the exec() family, like execvp()?
Edit: And I just noticed that system("cd") is also meaningless。Thanks for the help of everyone.
exec loads an executable file and replaces the current program image with it. As you rightly noted, cd is not an executable file, but rather a shell builtin. So the executable that you want to run is the shell itself. This is of course what system() does for you, but if you want to be explicit about it, you can use exec:
execl("/bin/sh", "-c", "cd", (const char *)0);
Since this replaces your current process image, you should do this after fork()ing off a new process.
However, this entire procedure has absolutely no effect. If you want to change the directory in your current process, use chdir().
You're better off using int chdir(const char *path); found in unistd.h.
No it is not, and it would be of no use. chdir (the function that changes a process's current directory) only affects the process that calls it (and its children). It does not affect its parent in particular.
So execing cd has no point, since the process would exit immediately after having changed directories.
(You could exec something like bash -c cd /tmp if you really want to, but as I said, this is fruitless.)
While, as already stated system("cd xxx") wouldn't change your application current directory, it is not completely useless.
You can still use system exit status to know if changing your current directory to the one stated would succeed or not.
Similarly, if you like complex solutions, you could also do the same with fork/exec, either with exec'ing /bin/sh -c cd xxx or simply /bin/cd xxx with OSes that provide an independent cd executable.
I would however recommend this non overkill faster equivalent access("xxx", X_OK|R_OK)
Note: All POSIX compliant OSes must provide an independent cd executable. This is at least the case with Solaris, AIX, HP-UX and Mac OS/X.
When a fork is done the environment variable CWD(current working directory) is inherited by the child from the parent.If fork and exec is done as usual then the child calls chdir() which simply changes the directory to the new directory and exits but this does not affect the parent.Hence, the new environment is lost..

Resources