I have the following c program.
$ cat main.c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
int fd;
if((fd = open(argv[1], O_RDONLY)) == -1) {
perror("open");
return 1;
}
if(close(fd) == -1) {
perror("close");
return 1;
}
return 0;
}
But I got the following error.
touch tmpfile
sudo chown root tmpfile
sudo chown root ./main_prog
sudo setcap cap_setuid+ep ./main_prog # There will be no error if I use sudo chmod u+s
./main_prog tmpfile
open: Permission denied
Could anybody show me how to use setcap for setuid?
What you are trying to do is access a file you need privilege to access. The cap_setuid capability does not directly grant this privilege - it grants the process the privilege to change its own UID(s). You can get there via this path, but it requires more code in your program.
The capability you want for your use case is one to override the discretionary access control: cap_dac_override.
With your ./main_prog as written, try this instead:
$ touch tmpfile
$ sudo chown root.root tmpfile
$ sudo chmod go-r tmpfile
$ ls -l tmpfile
-rw------- 1 root root 0 Apr 9 08:02 tmpfile
$ cat tmpfile
cat: tmpfile: Permission denied
$ sudo setcap cap_dac_override=ep ./main_prog
$ ./main_prog tmpfile
$ echo $?
0
Note, with capabilities, there is no need for main_prog to be owned by root.
Related
So I've got the following function, which executes another function (defined the same way, executed the same way this one is successfully called).
The reason for the syntax I've used here is to ensure the code is executed as a particular user- as I say, outside of functions, this syntax works with no issues.
f2(){
echo "test" > test.txt
}
f1(){
a=1
if (($a < 0 ))
then
echo "Not running function."
else
echo "Running function."
sudo su servuser -c "$(declare -f f2); f2"
fi
}
sudo su user1 -c "$(declare -f f1); f1"
Why is this the case? Is there a change that can be made to this code to allow me to call one function from another function as a certain user?
Any help would be most welcome :)
When you execute a script using su, that script has only access to the exported variables from your current shell script. You used $(declare -f f1) to work around this issue, but forgot that the 2nd su inside f1 loses access to f2. When f1 executes su user -c '$(declare -f f2); f2' there is no f2 so you cannot print/export it and therefore cannot execute it.
You have to explicitly include the dependencies of f1 in your first export:
f2() {
echo "test" > test.txt
}
f1() {
sudo su servuser -c "$(declare -f f2); f2"
}
sudo su user1 -c "$(declare -f f2 f1); f1"
By the way: Bash allows exporting functions.
f2() {
echo "test" > test.txt
}
f1() {
sudo su servuser -c f2
}
export -f f1 f2
sudo su user1 -c f1
I'm trying to do the opposite of "Detect if stdin is a terminal or pipe?".
I'm running an application that's changing its output format because it detects a pipe on STDOUT, and I want it to think that it's an interactive terminal so that I get the same output when redirecting.
I was thinking that wrapping it in an expect script or using a proc_open() in PHP would do it, but it doesn't.
Any ideas out there?
Aha!
The script command does what we want...
script --return --quiet -c "[executable string]" /dev/null
Does the trick!
Usage:
script [options] [file]
Make a typescript of a terminal session.
Options:
-a, --append append the output
-c, --command <command> run command rather than interactive shell
-e, --return return exit code of the child process
-f, --flush run flush after each write
--force use output file even when it is a link
-q, --quiet be quiet
-t[<file>], --timing[=<file>] output timing data to stderr or to FILE
-h, --help display this help
-V, --version display version
Based on Chris' solution, I came up with the following little helper function:
faketty() {
script -qfc "$(printf "%q " "$#")" /dev/null
}
The quirky looking printf is necessary to correctly expand the script's arguments in $# while protecting possibly quoted parts of the command (see example below).
Usage:
faketty <command> <args>
Example:
$ python -c "import sys; print(sys.stdout.isatty())"
True
$ python -c "import sys; print(sys.stdout.isatty())" | cat
False
$ faketty python -c "import sys; print(sys.stdout.isatty())" | cat
True
The unbuffer script that comes with Expect should handle this ok. If not, the application may be looking at something other than what its output is connected to, eg. what the TERM environment variable is set to.
Referring previous answer, on Mac OS X, "script" can be used like below...
script -q /dev/null commands...
But, because it may replace "\n" with "\r\n" on the stdout, you may also need script like this:
script -q /dev/null commands... | perl -pe 's/\r\n/\n/g'
If there are some pipe between these commands, you need to flush stdout. for example:
script -q /dev/null commands... | ruby -ne 'print "....\n";STDOUT.flush' | perl -pe 's/\r\n/\n/g'
I don't know if it's doable from PHP, but if you really need the child process to see a TTY, you can create a PTY.
In C:
#include <stdio.h>
#include <stdlib.h>
#include <sysexits.h>
#include <unistd.h>
#include <pty.h>
int main(int argc, char **argv) {
int master;
struct winsize win = {
.ws_col = 80, .ws_row = 24,
.ws_xpixel = 480, .ws_ypixel = 192,
};
pid_t child;
if (argc < 2) {
printf("Usage: %s cmd [args...]\n", argv[0]);
exit(EX_USAGE);
}
child = forkpty(&master, NULL, NULL, &win);
if (child == -1) {
perror("forkpty failed");
exit(EX_OSERR);
}
if (child == 0) {
execvp(argv[1], argv + 1);
perror("exec failed");
exit(EX_OSERR);
}
/* now the child is attached to a real pseudo-TTY instead of a pipe,
* while the parent can use "master" much like a normal pipe */
}
I was actually under the impression that expect itself does creates a PTY, though.
Updating #A-Ron's answer to
a) work on both Linux & MacOs
b) propagate status code indirectly (since MacOs script does not support it)
faketty () {
# Create a temporary file for storing the status code
tmp=$(mktemp)
# Ensure it worked or fail with status 99
[ "$tmp" ] || return 99
# Produce a script that runs the command provided to faketty as
# arguments and stores the status code in the temporary file
cmd="$(printf '%q ' "$#")"'; echo $? > '$tmp
# Run the script through /bin/sh with fake tty
if [ "$(uname)" = "Darwin" ]; then
# MacOS
script -Fq /dev/null /bin/sh -c "$cmd"
else
script -qfc "/bin/sh -c $(printf "%q " "$cmd")" /dev/null
fi
# Ensure that the status code was written to the temporary file or
# fail with status 99
[ -s $tmp ] || return 99
# Collect the status code from the temporary file
err=$(cat $tmp)
# Remove the temporary file
rm -f $tmp
# Return the status code
return $err
}
Examples:
$ faketty false ; echo $?
1
$ faketty echo '$HOME' ; echo $?
$HOME
0
embedded_example () {
faketty perl -e 'sleep(5); print "Hello world\n"; exit(3);' > LOGFILE 2>&1 </dev/null &
pid=$!
# do something else
echo 0..
sleep 2
echo 2..
echo wait
wait $pid
status=$?
cat LOGFILE
echo Exit status: $status
}
$ embedded_example
0..
2..
wait
Hello world
Exit status: 3
Too new to comment on the specific answer, but I thought I'd followup on the faketty function posted by ingomueller-net above since it recently helped me out.
I found that this was creating a typescript file that I didn't want/need so I added /dev/null as the script target file:
function faketty { script -qfc "$(printf "%q " "$#")" /dev/null ; }
There's also a pty program included in the sample code of the book "Advanced Programming in the UNIX Environment, Second Edition"!
Here's how to compile pty on Mac OS X:
man 4 pty # pty -- pseudo terminal driver
open http://en.wikipedia.org/wiki/Pseudo_terminal
# Advanced Programming in the UNIX Environment, Second Edition
open http://www.apuebook.com
cd ~/Desktop
curl -L -O http://www.apuebook.com/src.tar.gz
tar -xzf src.tar.gz
cd apue.2e
wkdir="${HOME}/Desktop/apue.2e"
sed -E -i "" "s|^WKDIR=.*|WKDIR=${wkdir}|" ~/Desktop/apue.2e/Make.defines.macos
echo '#undef _POSIX_C_SOURCE' >> ~/Desktop/apue.2e/include/apue.h
str='#include <sys/select.h>'
printf '%s\n' H 1i "$str" . wq | ed -s calld/loop.c
str='
#undef _POSIX_C_SOURCE
#include <sys/types.h>
'
printf '%s\n' H 1i "$str" . wq | ed -s file/devrdev.c
str='
#include <sys/signal.h>
#include <sys/ioctl.h>
'
printf '%s\n' H 1i "$str" . wq | ed -s termios/winch.c
make
~/Desktop/apue.2e/pty/pty ls -ld *
I was trying to get colors when running shellcheck <file> | less on Linux, so I tried the above answers, but they produce this bizarre effect where text is horizontally offset from where it should be:
In ./all/update.sh line 6:
for repo in $(cat repos); do
^-- SC2013: To read lines rather than words, pipe/redirect to a 'while read' loop.
(For those unfamiliar with shellcheck, the line with the warning is supposed to line up with the where the problem is.)
In order to the answers above to work with shellcheck, I tried one of the options from the comments:
faketty() {
0</dev/null script -qfc "$(printf "%q " "$#")" /dev/null
}
This works. I also added --return and used long options, to make this command a little less inscrutable:
faketty() {
0</dev/null script --quiet --flush --return --command "$(printf "%q " "$#")" /dev/null
}
Works in Bash and Zsh.
I have a file /a/b that is readable by a user A. But /a does not provide executable permission by A, and thus the path /a/b cannot traverse through /a. For an arbitrarily long path, how would I determine the cause for not being able to access a given path due to an intermediate path not being accessible by the user?
Alternative answer to parsing the tree manually and pinpointing the error to a single row would be using namei tool.
namei -mo a/b/c/d
f: a/b/c/d
drwxrw-rw- rasjani rasjani a
drw-rwxr-x rasjani rasjani b
c - No such file or directory
This shows the whole tree structure and permissions up until the entry where the permission is denied.
Something along like this:
#!/bin/bash
PAR=${1}
PAR=${PAR:="."}
if ! [[ "${PAR:0:1}" == / || "${PAR:0:2}" == ~[/a-z] ]]
then
TMP=`pwd`
PAR=$(dirname ${TMP}/${PAR})
fi
cd $PAR 2> /dev/null
if [ $? -eq 1 ]; then
while [ ! -z "$PAR" ]; do
PREV=$(readlink -f ${PAR})
TMP=$(echo ${PAR}|awk -F\/ '{$NF=""}'1|tr ' ' \/)
PAR=${TMP%/}
cd ${PAR} 2>/dev/null
if [ $? -eq 0 ]; then
if [ -e ${PREV} ]; then
ls -ld ${PREV}
fi
exit
fi
done
fi
Ugly but it would get the job done ..
So the idea is basicly that taking a parameter $1, if its not absolute directory, expand it to such and then drop the last element of the path and try to cd into it, if it fails, rince and repeat .. If it works, PREV would hold the last directory where user couldn't cd into, so print it out ..
Here's what I threw together. I actually didn't look at rasjani's answer before writing this, but it uses the same concept where you take the exit status of the command. Basically its going through all the directories (starting the farthest down the chain) and tries to ls them. If the exit status is 0, then the ls succeeded, and it prints out the last dir that it couldn't ls (I'm not sure what would happen in some of the edge cases like where you can't access anything):
LAST=/a/b
while [ ! -z "$LAST" ] ; do
NEXT=`echo "$LAST" | sed 's/[^\/]*$//' | sed 's/\/$//'`
ls "$NEXT" 2> /dev/null > /dev/null
if [ $? -eq 0 ] ; then
echo "Can't access: $LAST"
break
fi
LAST="$NEXT"
done
and I like putting stuff like this on one line just for fun:
LAST=/a/b; while [ ! -z "$LAST" ] ; do NEXT=`echo "$LAST" | sed 's/[^\/]*$//' | sed 's/\/$//'`; ls "$NEXT" 2> /dev/null > /dev/null; if [ $? -eq 0 ] ; then echo "Can't access: $LAST"; break; fi; LAST="$NEXT"; done
I have below C program for you which does this. Below are the steps
Copy and save program as file.c.
Compile program with gcc file.c -o file
Execute it as ./file PATH
Assuming that you have a path as /a/b/c/d and you do not have permission for 'c' then output will be
Given Path = /a/b/c/d
No permission on = /a/b/c
For permission i am relying on "EACCES" error. Path length is assumed to 1024.
If you have any question please share.
#include <stdio.h>
#include <string.h>
#include <errno.h>
#define MAX_LEN 1024
int main(int argc, char *argv[])
{
char path[MAX_LEN] = "/home/sudhansu/Test";
int i = 0;
char parse[MAX_LEN] = "";
if(argc == 2)
{
strcpy(path, argv[1]);
printf("\n\t\t Given Path = %s\n", path);
}
else
{
printf("\n\t\t Usage : ./file PATH\n\n");
return 0;
}
if(path[strlen(path)-1] != '/')
strcat(path, "/");
path[strlen(path)] = '\0';
while(path[i])
{
if(path[i] == '/')
{
strncpy(parse, path, i+1);
if(chdir(parse) < 0)
{
if(errno == EACCES)
{
printf("\t\t No permission on = [%s]\n", parse);
break;
}
}
}
parse[i] = path[i];
i++;
}
printf("\n");
return 0;
}
Regards,
Sudhansu
I am trying to create a executor program for regular users on linux with SUID bit set so whatever commands, passed to the program as parameters, get executed with root permission. However when I try to implement this as a bash script, this does not work, where it works when implemented in C. I want to know what I am doing wrong for the shell script. The codes are below
Shell Script:
#! /bin/bash
if [ $# -lt 1 ]; then
echo "Usage: $0 <Command String>"
exit 1
fi
$#
#Also tried this, same result
#exec $#
Execution:
root#: chmod 755 exec.sh
root#: chmod u+s exec.sh
root#: ll exec.sh
-rwsr-xr-x 1 root root 75 Sep 19 16:55 exec.sh
regular_user$: ./exec.sh whoami
regular_user
C Program:
#include <stdlib.h>
#include <stdio.h>
int main ( int argc, char *argv[] )
{
if ( argc < 2 ) {
printf( "Usage: %s <Command String>\n", argv[0] );
return 1;
}
else
{
argv[argc]=NULL;
//setuid(0); //Works without these
//setgid(0);
int exit=execvp(argv[1], argv+1);
return exit;
}
}
Execution:
root#: gcc exec.c -o exec.obj
root#: chmod 755 exec.obj
root#: chmod u+s exec.obj
root#: ll exec.obj
-rwsr-xr-x 1 root root 6979 Sep 19 17:03 exec.obj
regular_user$: ./exec.obj whoami
root
Both files have identical permissions
-rwsr-xr-x 1 root root 75 Sep 19 16:55 exec.sh
-rwsr-xr-x 1 root root 6979 Sep 19 17:03 exec.obj
It is documented in execve(2) :
Linux ignores the set-user-ID and set-group-ID bits on scripts.
IIRC, setuid scripts would be a significant security hole
See this question
You could configure sudo to avoid asking a password - see sudoers(5) (or use super)
You could also write a simple C program wrapping your shell script, and make it setuid.
try
regular_user$: sudo "./exec.sh whoami"
The reason is explain by RedHat at https://access.redhat.com/solutions/124693 :
When executing shell scripts that have the setuid bit (e.g., perms of rwsr-xr-x), the scripts run as the user that executes them, not as the user that owns them. This is contrary to how setuid is handled for binaries (e.g., /usr/bin/passwd), which run as the user that owns them, regardless of which user executes them.
In order to solve this issue I write a script utility which converts a script call to a native binary:
#!/bin/bash
# https://access.redhat.com/site/solutions/124693
if [ $# != 1 ]; then
echo "Please, provide script file name." >&2
exit 1
fi
if [ ${EUID} != 0 ]; then
echo "Only root can run this script." >&2
exit 1
fi
SCRIPT_FILE=$1
if [ ! -f "${SCRIPT_FILE}" ]; then
echo "Script file not found." >&2
exit 1
fi
SCRIPT_BASE_FILE=$(basename ${SCRIPT_FILE})
C_TEMPLATE=$(cat << DELIMITER
#include <libgen.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
char *cwd;
char exe_file[PATH_MAX];
char script_file[PATH_MAX];
readlink("/proc/self/exe", exe_file, PATH_MAX);
cwd = dirname(exe_file);
sprintf(script_file, "%s/${SCRIPT_BASE_FILE}", cwd);
setuid(0);
system(script_file);
return 0;
}
DELIMITER
)
C_FILE="${SCRIPT_FILE}.c"
EXE_FILE="${SCRIPT_FILE}.x"
echo "${C_TEMPLATE}" > "${C_FILE}" \
&& gcc "${C_FILE}" -o "${EXE_FILE}" \
&& chown root:root "${EXE_FILE}" \
&& chmod 4755 "${EXE_FILE}" \
&& rm "${C_FILE}" \
&& echo "Setuid script executable created as \"${EXE_FILE}\"."
I tried to use the read/write file descriptor in bash so that I could delete the file that the file descriptor referred to afterward, as such:
F=$(mktemp)
exec 3<> "$F"
rm -f "$F"
echo "Hello world" >&3
cat <&3
but the cat command gives no output. I can achieve what I want if I use separate file descriptors for reading and writing:
F=$(mktemp)
exec 3> "$F"
exec 4< "$F"
rm -f "$F"
echo "Hello world" >&3
cat <&4
which prints Hello world.
I suspected that bash doesn't automatically seek to the start of the file descriptor when you switch from writing to reading it, and the following combination of bash and python code confirms this:
fdrw.sh
exec 3<> tmp
rm tmp
echo "Hello world" >&3
exec python fdrw.py
fdrw.py
import os
f = os.fdopen(3)
print f.tell()
print f.read()
which gives:
$ bash fdrw.sh
12
$ # This is the prompt reappearing
Is there a way to achieve what I want just using bash?
I found a way to do it in bash, but it's relying on an obscure feature of exec < /dev/stdin which actually can rewind the file descriptor of stdin according to http://linux-ip.net/misc/madlug/shell-tips/tip-1.txt:
F=$(mktemp)
exec 3<> "$F"
rm -f "$F"
echo "Hello world" >&3
{ exec < /dev/stdin; cat; } <&3
The write descriptor isn't affected by that so you can still append output to descriptor 3 before the cat.
Sadly I only got this working under Linux not under MacOS (BSD), even with the newest bash version. So it doesn't seem very portable.
If you ever do happen to want to seek on bash file descriptors, you can use a subprocess, since it inherits the file descriptors of the parent process. Here is an example C program to do this.
seekfd.c
#define _FILE_OFFSET_BITS 64
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char* argv[])
{
/* Arguments: fd [offset [whence]]
* where
* fd: file descriptor to seek
* offset: number of bytes from position specified in whence
* whence: one of
* SEEK_SET (==0): from start of file
* SEEK_CUR (==1): from current position
* SEEK_END (==2): from end of file
*/
int fd;
long long scan_offset = 0;
off_t offset = 0;
int whence = SEEK_SET;
int errsv; int rv;
if (argc == 1) {
fprintf(stderr, "usage: seekfd fd [offset [whence]]\n");
exit(1);
}
if (argc >= 2) {
if (sscanf(argv[1], "%d", &fd) == EOF) {
errsv = errno;
fprintf(stderr, "%s: %s\n", argv[0], strerror(errsv));
exit(1);
}
}
if (argc >= 3) {
rv = sscanf(argv[2], "%lld", &scan_offset);
if (rv == EOF) {
errsv = errno;
fprintf(stderr, "%s: %s\n", argv[0], strerror(errsv));
exit(1);
}
offset = (off_t) scan_offset;
}
if (argc >= 4) {
if (sscanf(argv[3], "%d", &whence) == EOF) {
errsv = errno;
fprintf(stderr, "%s: %s\n", argv[0], strerror(errsv));
exit(1);
}
}
if (lseek(fd, offset, whence) == (off_t) -1) {
errsv = errno;
fprintf(stderr, "%s: %s\n", argv[0], strerror(errsv));
exit(2);
}
return 0;
}
No. bash does not have any concept of "seeking" with its redirection. It reads/writes (mostly) from beginning to end in one long stream.
When you open a file descriptor in bash like that, it becomes accessible as a file in /dev/fd/.
On that you can do cat and it'll read from the start, or append (echo "something" >> /dev/fd/3), and it'll add it to the end.
At least on my system it behaves this way. (On the other hand, I can't seem to be able to get "cat <&3" to work, even if I don't do any writing to the descriptor).
Try changing the sequence of commands:
F=$(mktemp tmp.XXXXXX)
exec 3<> "$F"
echo "Hello world" > "$F"
rm -f "$F"
#echo "Hello world" >&3
cat <&3
#!/bin/bash
F=$(mktemp tmp.XXXXXX)
exec 3<> $F
rm $F
echo "Hello world" >&3
cat /dev/fd/3
As suggested in other answer, cat will rewind the file descriptor for you before reading from it since it thinks it's just a regular file.
To 'rewind' the file descriptor, you can simply use /proc/self/fd/3
Test script :
#!/bin/bash
# Fill data
FILE=test
date +%FT%T >$FILE
# Open the file descriptor and delete the file
exec 5<>$FILE
rm -rf $FILE
# Check state of the file
# should return an error as the file has been deleted
file $FILE
# Check that you still can do multiple reads or additions
for i in {0..5}; do
echo ----- $i -----
echo . >>/proc/self/fd/5
cat /proc/self/fd/5
echo
sleep 1
done
Try to kill -9 the script while it is running, you will see that contrary to what happens with the trap method, the file is actually deleted.
Expansion on the answer by #sanmai...
And confirmation of what is going on...
#/bin/bash
F=$(mktemp tmp.XXXXXX)
exec 3<>$F # open the temporary file for read and write
rm $F # delete file, though it remains on file system
echo "Hello world!" >&3 # Add a line to a file
cat /dev/fd/3 # Read the whole file
echo "Bye" >>/dev/fd/3 # Append another line
cat /dev/fd/3 # Read the whole file
echo "Goodbye" >&3 # Overwrite second line
cat /dev/fd/3 # Read the whole file
cat <&3 # Try to Rewind (no output)
echo "Cruel World!" >&3 # Still adds a line on end
cat /dev/fd/3 # Read the whole file
shell_seek 3 6 0 # seek fd 3 to position 6
echo -n "Earth" >&3 # Overwrite 'World'
shell_seek 3 # rewind fd 3
cat <&3 # Read the whole file put 3 at end
Note that the echo Goodbye overwrites the second lineas the file descriptor &3 had not changed by the cat!
So I tried using cat <&3 which did not output anything, probably as the file descriptor was at the end of the file. To see it if it rewinds the descriptor it was given. It does not.
The last part is to use the 'C' program that was provided, compiled and named shell_seek and yes it seems it works as the first 'World' was replaced by 'Earth', the rewind (seek to start) worked allowing the last cat to again read the whole file. It would put the fd at the end of the file again!
Doing it using perl instead of C was not that hard either.
For example perl -e 'open(FD,">&3"); seek(FD,0,0);' will rewind file descriptor 3 back to the start of the file.
I have now made a perl version of shell_seek so I don't have to re-compile it all the time for different systems. Not only that but the script can also 'tell' you the current file descriptor offset, and also 'truncate' the file that file descriptor points too. Both operations are commonly used when using seek, so it seemed a good idea to include those functions. You can download the script from...
https://antofthy.gitlab.io/software/#shell_seek