chroot + execvp + bash - bash

Update
Got it! See my solution (fifth comment)
Here is my problem:
I have created a small binary called "jail" and in /etc/password I have made it the default shell for a test user.
Here is the -- simplified -- source code:
#define HOME "/home/user"
#define SHELL "/bin/bash"
...
if(chdir(HOME) || chroot(HOME)) return -1;
...
char *shellargv[] = { SHELL, "-login", "-rcfile", "/bin/myscript", 0 };
execvp(SHELL, shellargv);
Well, no matter how hard I try, it seems that, when my test user logs in, /bin/myscript will never be sourced. Similarly, if I drop a .bashrc file in user's home directory, it will be ignored as well.
Why would bash snob these guys?
--
Some precisions, not necessarily relevant, but to clear out some of the points made in the comments:
The 'jail' binary is actually suid, thus allowing it to chroot() successfully.
I have used 'ln' to make the appropriate binaries available - my jail cell is nicely padded :)
The issue does not seem to be with chrooting the user...something else is remiss.

As Jason C says, the exec'ed shell isn't interactive.
His solution will force the shell to be interactive if it accepts -i to mean that (and bash does):
char *shellargv[] = { SHELL, "-i", "-login", ... };
execvp(SHELL, shellargv);
I want to add, though, that traditionally a shell will act as a login shell if ARGV[0] begins with a dash.
char *shellargv[] = {"-"SHELL, "-i", ...};
execvp(SHELL, shellargv);
Usually, though, Bash will autodetect whether it should run interactively or not. Its failure to in your case may be because of missing /dev/* nodes.

The shell isn't interactive. Try adding -i to the list of arguments.

I can identify with wanting to do this yourself, but if you haven't already, check out jail chroot project and jailkit for some drop in tools to create a jail shell.

By the time your user is logging in and their shell tries to source this file, it's running under their UID. The chroot() system call is only usable by root -- you'll need to be cleverer than this.
Also, chrooting to a user's home directory will make their shell useless, as (unless they have a lot of stuff in there) they won't have access to any binaries. Useful things like ls, for instance.

Thanks for your help, guys,
I figured it out:
I forgot to setuid()/setgid(), chroot(), setuid()/setgid() back, then pass a proper environment using execve()
Oh, and, if I pass no argument to bash, it will source ~/.bashrc
If I pass "-l" if will source /etc/profile
Cheers!

Related

Mistake this is a duplicate [duplicate]

This question already has answers here:
How to obtain the first letter in a Bash variable?
(7 answers)
Closed 3 years ago.
I am trying to my a custom terminal command. I just learned I am supposed to do it using the Unix script? I don't really know much of what that is and am still trying to figure it out. What I do know is that $1 is an arg is it possible to make it a variable and then get the first letter like you could in python?
EX:
str = 'happy'
str[0] = 'h'
You're asking a few different things here.
I am trying to my a custom terminal command.
That could mean a few different things, but the most obvious meaning is that you want to add an executable to your path so that when you type it at the terminal, it runs just like any other executable on your system. This requires just a few things:
the executable permission must be set.
the file must specify how it can be executed. For interpreted programs such as bash scripts or python scripts, you can do so by beginning the file with a "shebang line" that specifies the interpreter for the file.
the file must be in one of the locations specified by your $PATH.
I just learned I am supposed to do it using the Unix script?
there's no such thing as a "unix script", but what you seem to be referring to is a "shell script". Though these are commonly associated with unix, they're no more inherently a unix script than any other language. A shell, such as bash, sh, or any other, is just an interpreted language that is designed so that it is convenient to be used interactively by a human as well as being programmatically executed as part of a saved file.
I don't really know much of what that is and am still trying to figure it out.
Let's get into some specifics.
First I edit a file called 'hello-world' to contain:
#!/bin/bash
echo "Hello, world!"
Note that this filename has no "extension". Though heuristics based on file extension are sometimes used (espeically in windows) to determine a file type, unix typically sees a file "extension" as part of the arbitrary file name. The thing that makes this a potentially executable bash script is the specification of that interpreter on the shebang line.
We can run our script right now from bash, just as we could if we wrote a python script.
$ bash hello-world
hello, world!
To make the bash implicit, we mark the file as executable. This enables the linux operating system to consult the beginning "magic bytes" of the file to determine how to run it. Thes beginning bytes might signify an ELF file (a compiled executable, written in eg C, C++, or go). Or, it might be #! which just so happens means , "read the rest of this first line to determine the command to run, and pass the rest of this file into that command to be interpreted.
$ chmod +x hello-world
ls -l will show us the "permissions" on the file (more accurately called the "file mode", hence chmod rather than chperm) . The x stands for executable, so we have enabled the use of the leading bytes to determine method of execution. Remember, the first two bytes of this file, and the rest of that first line, then specify that this file should be "run through bash" so to speak.
$ ls -l hello-world
-rwxr-xr-x 1 danfarrell staff 33 Dec 27 20:02 hello-world
Now we can run the file from the current directory:
$ ./hello-world
hello, world!
At this point, the only difference between this command and any other on the system, is that you have to specify its location. That's because my current directory is not in the system path. In short, the path (accessible in a unix shell via the $PATH variable) specifies an ordered list of locations that should be searched for a specified command whose location is not otherwise specified.
For example, there's a very common program called whoami. I can run it directly from my terminal without specifying a location of the executable:
$ whoami
danfarrell
This is because there's a location in my $PATH in which the shell was able to find that command. Let's take a closer look. First, here's my path:
$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/go/bin
And there's also a convenient program called whereis which can help show which path elements supply a named executable:
$ whereis whoami
/usr/bin/whoami
Sure enough, whoami is in one of the elements of the $PATH. (Actually I shared a simplified $PATH. Yours might be somewhat longer).
Finally, then, we can get to the last thing. If I put hello-world in one of the $PATH elements, I will be able to invoke it without a path. There are two ways to do this: we can move the executable to a location specified in the path, or we can add a new location to the path. For simplicity's sake I'll choose the first of these.
$ sudo cp hello-world /usr/local/bin/
Password:
I needed to use sudo to write to /usr/local/bin because it's not accessible as my user directly - that's quite standard.
Finally, I've achieved the goal of being able to run my very important program from any location, without specifying the executable's location.
$ hello-world
hello, world!
$ which hello-world
/usr/local/bin/hello-world
It works! I've created what might be described as a "custom terminal command".
What I do know is that $1 is an arg is it possible to make it a variable and then get the first letter like you could in python?
Well, one option would be to simply write the custom terminal command in python. If python is available,
$ which python
/usr/bin/python
You can specify it in a shebang just like a shell can be:
#!/usr/bin/env python
print("hello, world!"[0])
$ hello-world
h
it works!
Okay, confession time. I actually used #!/usr/bin/env python, not /usr/bin/python. env helps find the correct python to use in the user's environment, rather than hard coding one particular python. If you've been using python during the very long running python 2 to python 3 migration, you can no doubt understand why I"m reticent to hard code a python executable in my program.
It's certainly possible to get the first letter of a string in a bash script. But it's also very possible to write a custom command in a program other than shell. Python is an excellent choice for string manipulation, if you know it. I often use python for shell one-liners that need to interact with json, a format that doesn't lend itself well to standard unix tool stream editing.
Anyway, at the expense of incurring SO community's ire by reanswering an "already answered" question, I'll include a version in shell (Credit goes to David C Rankin)
#!/bin/bash
echo "${1:0:1}"
$ hello-world hiworld
h

Launch interactive Bash shell in Ruby script, with initial command

I'm working on an interactive Ruby script, which build and packages resources. In the middle of the process, I'd like to drop into an interactive shell, but pre-cd'd into a specific working directory, as well as with an explanatory message (CTRL-D to continue). The interactive bash + given initial command is what's problematic.
Per the answer for doing something like this in Bash, given at https://stackoverflow.com/a/36152028, I've tried
system '/bin/bash', '--init-file', '<(echo "cd ~/src/devops; pwd")'
However, bash runs interactively but completely ignores the '<(echo "cd ~/src/devops; pwd")' section.
Interestingly system '/bin/bash', '--init-file complains if no argument is given, but literally anything runs bash, but with no initial command.
*Note that (--rcfile instead of --init-file) has the same effect.
Change the working directory of the Ruby script first, so that bash inherits the correct working directory.
curr_dir = Dir.pwd
Dir.chdir("#{Dir.home}/src/devops")
system "/bin/bash"
Dir.chdir(curr_dir) # Restore the original working directory if desired
Oh, this is probably far better (you can probably guess how little familiarity I have with Ruby):
system("/bin/bash", :chdir=>"#{Dir.home}/src/devops")

Simple Linux shred script error

I have an ultra-simple script that houses the Linux program shred, and it contains the parameters that have always worked from the command line (bash). Specifically 'shred -uzn 35'
The script, named D, has execute permissions set.
When I run the script, bash prints an error:
$ D some_file_to_delete
shred: missing file operand
I realize that the solution to the problem is probably as simple as the program itself. Please help?
Thanks in advance.
EDIT: The error "missing file operand" was due to the fact that the script was not set to take arguments, such as via "$#". Also, as stated in the accepted answer, I agree that an alias makes total sense for such a scenario (much more sense than, say, a script somewhere in $PATH).
Since you are using a script, not an alias, you need to pass the arguments through
shred -uzn 35 "$#"
In this case, however, I suggest you do make it an alias. In your .bashrc file, add this:
alias D='shred -uzn 35'

Can a shell script indicate that its lines be loaded into memory initially?

UPDATE: this is a repost of How to make shell scripts robust to source being changed as they run
This is a little thing that bothers me every now and then:
I write a shell script (bash) for a quick and dirty job
I run the script, and it runs for quite a while
While it's running, I edit a few lines in the script, configuring it for a different job
But the first process is still reading the same script file and gets all screwed up.
Apparently, the script is interpreted by loading each line from the file as it is needed. Is there some way that I can have the script indicate to the shell that the entire script file should be read into memory all at once? For example, Perl scripts seem to do this: editing the code file does not affect a process that's currently interpreting it (because it's initially parsed/compiled?).
I understand that there are many ways I could get around this problem. For example, I could try something like:
cat script.sh | sh
or
sh -c "`cat script.sh`"
... although those might not work correctly if the script file is large and there are limits on the size of stream buffers and command-line arguments. I could also write an auxiliary wrapper that copies a script file to a locked temporary file and then executes it, but that doesn't seem very portable.
So I was hoping for the simplest solution that would involve modifications only to the script, not the way in which it is invoked. Can I just add a line or two at the start of the script? I don't know if such a solution exists, but I'm guessing it might make use of the $0 variable...
The best answer I've found is a very slight variation on the solutions offered to How to make shell scripts robust to source being changed as they run. Thanks to camh for noting the repost!
#!/bin/sh
{
# Your stuff goes here
exit
}
This ensures that all of your code is parsed initially; note that the 'exit' is critical to ensuring that the file isn't accessed later to see if there are additional lines to interpret. Also, as noted on the previous post, this isn't a guarantee that other scripts called by your script will be safe.
Thanks everyone for the help!
Use an editor that doesn't modify the existing file, and instead creates a new file then replaces the old file. For example, using :set writebackup backupcopy=no in Vim.
How about a solution to how you edit it.
If the script is running, before editing it, do this:
mv script script-old
cp script-old script
rm script-old
Since the shell keep's the file open as long as you don't change the contents of the open inode everything will work okay.
The above works because mv will preserve the old inode while cp will create a new one. Since a file's contents will not actually be removed if it is opened, you can remove it right away and it will be cleaned up once the shell closes the file.
According to the bash documentation if instead of
#!/bin/bash
body of script
you try
#!/bin/bash
script=$(cat <<'SETVAR'
body of script
SETVAR)
eval "$script"
then I think you will be in business.
Consider creating a new bang path for your quick-and-dirty jobs. If you start your scripts with:
#!/usr/local/fastbash
or something, then you can write a fastbash wrapper that uses one of the methods you mentioned. For portability, one can just create a symlink from fastbash to bash, or have a comment in the script saying one can replace fastbash with bash.
If you use Emacs, try M-x customize-variable break-hardlink-on-save. Setting this variable will tell Emacs to write to a temp file and then rename the temp file over the original instead of editing the original file directly. This should allow the running instance to keep its unmodified version while you save the new version.
Presumably, other semi-intelligent editors would have similar options.
A self contained way to make a script resistant to this problem is to have the script copy and re-execute itself like this:
#!/bin/bash
if [[ $0 != /tmp/copy-* ]] ; then
rm -f /tmp/copy-$$
cp $0 /tmp/copy-$$
exec /tmp/copy-$$ "$#"
echo "error copying and execing script"
exit 1
fi
rm $0
# rest of script...
(This will not work if the original script begins with the characters /tmp/copy-)
(This is inspired by R Samuel Klatchko's answer)

Bash Daemon Named "sh" or "sleep" not the filename

I have created a simple bash script, chmod +x, and successfully am running it as a background service.
But, the script is called "sh" or "sleep" or whatever command seems to be running at the time, not my script name, when I view a process list.
How do I name the process of my bash script so I can distinguish it? I want to be sure that I'm not running the script more than once.
I am very new to bash scripting... sorry if this is a dumb question.
I am using #!/bin/bash
Do you have a "shebang" in your script?
I just did a little test. I found that with no shebang, the test script showed in ps as whatever command was executing at the time. However, if I, as I usually do, put:
#!/bin/bash
or
#!/bin/sh
(which on my system is symlinked to /bin/dash) as the first line in the script then the script showed up under its own name in the output of ps.
Your parent shell will be running the whole time. That will be sh. Any other processes spawned by that shell will also be running. Try pstree to show parent-child relationships.
BTW, if you use bash-specific features that aren't in POSIX Bourne shell, you should use #!/bin/bash, not #!/bin/sh. Some systems have bash, but have a lighter-weight /bin/sh.
I am very new to bash scripting... sorry if this is a dumb question.
Not dumb. Basic, but only once you understand how Unix processes work, (and how whatever you're using in OS X that shows you "service" names, since that's not a word that would make sense in any Unix context in this situation.) So you're dealing with a fair amount of complexity, and I don't blame you for asking.
Maybe OS X looks at process group leaders or something to come up with a "service name", if that's what it really calls it. I think that would be the process name of whatever process is running in the foreground (i.e. that you didn't fork off with & at the end of it, so the shell is waiting for it before executing the next command.)
Since that's what ps also shows I have a hunch you're out of luck. Sorry but shell scripts can't change their apparent process name.
However, for the cases that show bash you can create a symlink to bash under a name descriptive to your script and invoke your script via that symlink.
Not sure how portable my solution will be, but it works on Linux.
If you really want to do this (maybe you want to be able to kill your process by looking it up by name), you can write a small C program to call into the shell with a different process name.. For example...
#include <unistd.h>
#include <stdio.h>
int main(int argc, char **argv)
{
if (argv[1])
{
argv[0] = argv[1];
return execvp("sh", argv);
}
else
{
fprintf(stderr, "usage: %s <script> [args]\n", argv[0]);
return 1;
}
}
Say that's called wrapper.c. You can compile with:
gcc -o wrapper wrapper.c
Then you can run:
./wrapper ./my-script
And check top or ps. It should have a "forged" program name..
Now... Whether you actually want to do this? I don't know. It's probably not worth it. Most people don't bother with this sort of thing.

Resources