unshare user namespace, fork, map uid then execvp failing - c++

I am trying to do the following sequence of actions:
unshare the user namespace;
Map the user in child process to root;
execvp.
However, when running id, my code outputs the user as a nobody or fails without error.
#include <sched.h>
#include <cstdio>
#include <cstring>
#include <cerrno>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/mount.h>
#include <system_error>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
void unshare_user_namespace() {
if (0 != unshare(CLONE_NEWUSER)) {
fprintf(stderr, "%s\n", "USER unshare has failed");
exit(1);
}
}
void map_id() {
int pid = getpid();
char file[100];
if (0 > sprintf(file, "/proc/%d/uid_map", pid)) {
printf("Couldn't sprintf uid_map path.");
exit(1);
}
int fd;
fd = open(file, 1);
if (fd < 0) {
printf("Coudln't open file for writing.\n");
exit(1);
}
int uid = getuid();
char * buf;
if (0 > sprintf(buf, "0 %d 1", uid)) {
printf("Couldn't sprintf uid_map content.");
exit(1);
}
if (write(fd, buf, strlen(buf))) {
printf("Coudln't write mapping into file.\n");
exit(1);
}
free(buf);
close(fd);
}
void start(char * command, char ** args) {
unshare_user_namespace();
int fork_pid = fork();
if (-1 == fork_pid) {
fprintf(stderr, "%s\n", "couldn't fork");
exit(1);
}
if (0 == fork_pid) {
map_id();
if (-1 == execvp(command, args)) {
fprintf(stderr, "%s\n", "couldn't execvp");
exit(1);
}
}
}
int main(int argc, char ** argv) {
start(argv[1], & argv[1]);
int status;
wait( & status);
return 0;
}
I tried reading the man pages for namespaces, unshare etc but couldn't figure out what's wrong with my code.
To run the code:
$ g++ <file_containing_code> && ./a.out id

Pretty sure you've already found the answer, but this is a minimal sample I could come up with:
// gcc -Wall -std=c11
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <sched.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <stdarg.h>
void write_to_file(const char *which, const char *format, ...) {
FILE * fu = fopen(which, "w");
va_list args;
va_start(args, format);
if (vfprintf(fu, format, args) < 0) {
perror("cannot write");
exit(1);
}
fclose(fu);
}
int main(int argc, char ** argv) {
// array of strings, terminated with NULL entry
char **cmd_and_args = (char**) calloc(argc, sizeof(char*));
for (int i = 1 ; i < argc; i++) {
cmd_and_args[i-1] = argv[i];
}
uid_t uid = getuid();
gid_t gid = getgid();
// first unshare
if (0 != unshare(CLONE_NEWUSER)) {
fprintf(stderr, "%s\n", "USER unshare has failed");
exit(1);
}
// remap uid
write_to_file("/proc/self/uid_map", "0 %d 1", uid);
// deny setgroups (see user_namespaces(7))
write_to_file("/proc/self/setgroups", "deny");
// remap gid
write_to_file("/proc/self/gid_map", "0 %d 1", gid);
// exec the command
if (execvp(cmd_and_args[0], cmd_and_args) < 0) {
perror("cannot execvp");
exit(1);
}
// unreachable
free(cmd_and_args);
return 0;
}

Related

macOS iostream on a pseudo tty does not work

GCC has a stdio_filebuf extension that lets you wrap an iostream around a file descriptor while clang's basic_filebuf has an __open() method that lets open a file descriptor. I've tested both on regular files on both linux and macos. Works as expected.
However, for a pseudo tty file descriptor, it works for linux but not macos. I've confirmed that the file descriptor on macos does work by using read(2) on it. Here's my code.
#include <fmt/format.h>
#include <getopt.h>
#include <vector>
#include <functional>
#include <future>
#include <fcntl.h>
#include <cstdlib>
#if defined(__linux__)
#include <ext/stdio_filebuf.h>
#elif defined(__APPLE__)
#include <fstream>
#else
#error "unsupported os"
#endif
#include <iostream>
#include <unistd.h>
int get_ptym(int oflags)
{
int fd;
if ((fd = posix_openpt(oflags)) == -1
or grantpt(fd) == -1
or unlockpt(fd) == -1)
return -1;
return fd;
}
ssize_t getdelim(int fd, char *buf, size_t bufsz,
char delim='\n', bool store_delim=false)
{
size_t j = 0;
while (j < bufsz-2) {
char c;
ssize_t rc;
if ((rc = ::read(fd, &c, 1)) == -1) return rc;
if (rc == 0) break;
if (c != delim) { buf[j++] = c; continue; }
// Break if we get the delimiter.
if (store_delim) buf[j++] = c;
break;
}
buf[j] = '\0';
return j+1;
}
std::string m0(int fd)
{
#if defined(__linux__)
__gnu_cxx::stdio_filebuf<char> fb(fd, std::ios::in|std::ios::out);
std::iostream ioob(&fb);
#endif
#if defined(__APPLE__)
std::basic_filebuf<char> fb;
std::iostream ioob(fb.__open(fd, std::ios::in|std::ios::out));
#endif
std::string s;
if (!std::getline(ioob, s, '\n').good())
throw std::runtime_error("m0: getline failed");
return s;
}
std::string m1(int fd)
{
char buf[4096];
ssize_t nrd;
if ((nrd = getdelim(fd, buf, sizeof(buf))) == -1)
throw std::runtime_error("getlim error");
return std::string(buf);
}
void testing(const std::string &foonm, std::function<std::string(int)> foo)
{
using namespace std::chrono_literals;
int mfd, sfd;
// Open pty master and slave.
if ((mfd = get_ptym(O_RDWR)) == -1) return;
if ((sfd = open(ptsname(mfd), O_RDWR)) == -1) return;
#if defined(__linux__)
__gnu_cxx::stdio_filebuf<char> fb(sfd, std::ios::in|std::ios::out);
std::iostream ioob(&fb);
#endif
#if defined(__APPLE__)
std::basic_filebuf<char> fb;
std::iostream ioob(fb.__open(sfd, std::ios::in|std::ios::out));
#endif
fmt::print("testing {}\n", foonm);
std::string s;
if (!std::getline(std::cin, s, '\n').good()) {
fmt::print("{}: getline failed\n", __func__);
return;
}
auto f = std::async(std::launch::async, foo, mfd);
ioob << s << '\n';
ioob.flush();
try {
std::string fget;
std::future_status fstatus;
fstatus = f.wait_for(8s);
switch (fstatus) {
case std::future_status::timeout:
fmt::print("{}: f.wait_for(8s) timed out\n", __func__);
break;
case std::future_status::ready:
fget = f.get();
fmt::print("{}: fstatus ready, f.get={}\n", __func__, fget);
break;
default:
fmt::print("{}: fstatus default??\n", __func__);
}
}
catch (std::exception &e) {
fmt::print("{}: exception \"{}\"\n", __func__, e.what());
}
}
int main(int argc, char *argv[])
{
testing("m1", m1);
testing("m0", m0);
return 0;
}
Any ideas?

c++ pipe buffering disable

How to disable buffering in pipe. I'm creating a simple recorder/player for I/O. To do this I need record output witch time delays.
To record delays, I need something like this
example tekst
"wait 1s"
example tekst
"wait 1s"
example tekst
...
but if I use
pipe2(in, O_DIRECT );
i see something like this
"wait 100s"
"100 times" example tekst
"wait 100s"
"100 times" example tekst
...
man7 tells:
O_DIRECT (since Linux 3.4)
Create a pipe that performs I/O in "packet" mode. Each
write(2) to the pipe is dealt with as a separate packet, and
read(2)s from the pipe will read one packet at a time.
I tried to disable buffering, by:
fcntl(in[1], F_SETPIPE_SZ, 1);
but it's still not working.
read.cpp
#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <poll.h>
#include <assert.h>
bool keep = true;
void intHandler(int dummy) {
keep = 0;
}
int main(void) {
signal(SIGINT, intHandler);
int in[2];
int out[2];
int pid;
int fo;
char buf[1024];
pipe2(in, O_DIRECT );
pipe2(out, O_DIRECT );
pid = fork();
if (pid == -1) {
perror("fork");
exit(1);
}
if (pid == 0) {
close(in[0]);
close(out[1]);
close(0);
close(1);
dup2(in[1], 1);
dup2(out[0], 0);
close(in[1]);
close(out[0]);
sleep(1);
char *newargv[] = {"/root/Pulpit/a1", NULL, NULL, NULL};
char *newenviron[] = {NULL};
int ret = execve("/root/Pulpit/a1", newargv, newenviron);
printf("%d", ret);
return 0;
} else {
close(out[0]);
close(in[1]);
int n = 0;
while (keep) {
int wyn = read(in[0], buf, 1024);
if (wyn > 0) {
char aa[1024];
write(1, buf, wyn);
fsync(1);
}
}
}
return (0);
}
a1.cpp
#include <cstdlib>
#include <unistd.h>
#include "stdio.h"
using namespace std;
/*
*
*/
int main(int argc, char** argv) {
while(true){
printf("example text\n");
usleep(100000);
}
return 0;
}

C++ - msgsnd & msgrcv communication between 2 different programs

I have two programs and I want them to communicate together by msgrcv() && msgsnd(). I so have a master program which init the message queue and start the 2 others programs:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string>
#include <iostream>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <stdio.h>
int main() {
int qid = msgget(ftok(".",'u'), 0);
char* params[3];
params[1] = (char *)malloc(sizeof(char) * 9);
sprintf(params[1], "%d", qid);
params[2] = NULL;
printf("qid = %d and qid(str) = %s", qid, params[1]);
// return (0);
//spawning two child processes
pid_t cpid = fork();
if (cpid == 0) {
params[0] = (char*)"./sender";
execv(params[0], params);
exit(0);
}
cpid = fork();
if (cpid == 0) {
params[0] = (char*)"./receiver";
execv(params[0], params);
exit(0);
}
while (wait(NULL) != -1); // waiting for both children to terminate
msgctl(qid, IPC_RMID, NULL);
std::cout << "parent proc: " << getpid()
<< " now exits" << std::endl;
exit(0);
}
I also prepare the parameters and start the both following programs:
sender
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include <iostream>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(int ac, char **av) {
if (ac != 2)
return (-1);
// create my msgQ with key value from ftok()
// int qid = msgget(IPC_PRIVATE, IPC_EXCL|IPC_CREAT|0600);
int qid = atoi(av[1]);
// declare my message buffer
struct buf {
long mtype; // required
char greeting[50]; // mesg content
};
buf msg;
int size = sizeof(msg)-sizeof(long);
std::cout << "Welcome in the prog assignment 2! Type [exit] to stop the program." << std::endl;
bool exit = false;
while (!exit)
{
std::cout << getpid() << ": ";
std::cin.getline(msg.greeting, 50, '\n');
std::cout << msg.greeting << std::endl;
msg.mtype = 114; // only reading mesg with type mtype = 114
if (strcmp(msg.greeting, "exit") == 0)
exit = true;
msgsnd(qid, (struct msgbuf *)&msg, size, 0);
}
}
receiver
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include <iostream>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <stdio.h>
int main(int ac, char **av) {
int i = 0;
while (i < ac)
printf("AV: %s\n", av[i++]);
if (ac != 2)
return (-1);
// int qid = msgget(IPC_PRIVATE, IPC_EXCL|IPC_CREAT|0600);
int qid = atoi(av[1]);
// declare my message buffer
struct buf {
long mtype;
char greeting[50];
};
buf msg;
int size = sizeof(msg)-sizeof(long);
bool exit = false;
while (!exit)
{
msgrcv(qid, (struct msgbuf *)&msg, size, 114, 0);
if (strcmp(msg.greeting, "exit") == 0)
exit = true;
std::cout << getpid() << msg.greeting << std::endl;
}
std::cout << "get out" << std::endl;
}
It doesn't work and I'm not sure to understand why because, I'm creating the message queue, passing it as parameter, then I put it back as int and then use it. However, it just gives me an infinite loop of weird display, why?
ANy help is welcome.. Thank !

Open TTY to use with execlp and dup

I am trying to create a minimal code to use pipe/fork/execlp.
So far so good, I am using execlp with bash -c, so if I do.
echo asd |./a.out cat
> asd
So it is working as expected.
But if I try to use anything that needs a TTY, it does not work.
Like ./a.out vim, I get "Vim: Warning: Input is not from a terminal"
And the vim that was open does not works as expected.
I tried to find on the internet an example on how to open a TTY, the only one that I found was:
http://www.danlj.org/lad/src/minopen.c
My Code, so far is:
#include <iostream>
#include <cstdio>
#include <string.h>
#include <cstdlib>
#include <unistd.h>
#include <sys/wait.h>
typedef struct pCon{
int fout[2];
int fin[2];
int fd[2];
int pid1, pid2;
} connectionManager;
std::string command = "";
/*
* Implementation
*/
void childFork(connectionManager *cm);
int main(int argc, char *argv[]) {
int size;
if(argc < 2) exit(1);
else command = argv[1];
connectionManager *cm = new connectionManager;
pipe(cm->fd);
if((cm->pid1 = fork()) == -1)exit(1);
if (cm->pid1 == 0)
{
const unsigned int RCVBUFSIZE = 2000;
char echoString[RCVBUFSIZE];
while((size = read(fileno(stdin),echoString,RCVBUFSIZE)) > 0)
write(cm->fd[1], echoString, size);
close(cm->fd[1]);
}
else
childFork(cm);
return 0;
}
void childFork(connectionManager *cm){
char *buffer = new char[2000];
int size;
close(cm->fd[1]);
dup2(cm->fd[0], 0);
close(cm->fd[0]);
pipe(cm->fout);
if((cm->pid2 = fork()) == -1)exit(1);
if (cm->pid2 == 0)
{
close(cm->fout[0]);
int returnCode = execlp("bash", "bash", "-c", command.c_str(), NULL);
if(returnCode!=0)
std::cerr << "Error starting the bash program" << std::endl;
}
else
{
close(cm->fout[1]);
while((size = read(cm->fout[0], buffer, 2000 )) > 0 )
write(fileno(stdout), buffer, size);
}
}
I tried to keep the minimal necessary code to make it work.
Is there any way to implement TTY on this code, I know that does not seems to be such trivial task.
Can someone help me with that?
I also tried to open the tty and dup it, but no luck so far.
Try to use pseudo terminal. You can use opentty. For your purpose you can use forkpty which combines pty with fork. I've created a small example for you. About the same as your program, just it works. I've kept it simple, so I don't handle the terminal control characters.
#include <pty.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/select.h>
int main(int argc, char *argv[])
{
if (argc<1) return 1;
int master;
pid_t pid = forkpty(&master, NULL, NULL, NULL); // opentty + login_tty + fork
if (pid < 0) {
return 1; // fork with pseudo terminal failed
}
else if (pid == 0) { // child
char *args[] = { argv[1], argv[2], NULL }; // prg + 1 argument
execvp(argv[1], args); // run the program given in first param
}
else { // parent
struct termios tios;
tcgetattr(master, &tios);
tios.c_lflag &= ~(ECHO | ECHONL);
tcsetattr(master, TCSAFLUSH, &tios);
while(1) {
fd_set read_fd, write_fd, err_fd;
FD_ZERO(&read_fd);
FD_ZERO(&write_fd);
FD_ZERO(&err_fd);
FD_SET(master, &read_fd);
FD_SET(STDIN_FILENO, &read_fd);
select(master+1, &read_fd, &write_fd, &err_fd, NULL);
if (FD_ISSET(master, &read_fd))
{
char ch;
int c;
if (c=read(master, &ch, 1) != -1) // read from program
write(STDOUT_FILENO, &ch, c); // write to tty
else
break; // exit when end of communication channel with program
}
if (FD_ISSET(STDIN_FILENO, &read_fd))
{
char ch;
int c=read(STDIN_FILENO, &ch, 1); // read from tty
write(master, &ch, c); // write to program
}
}
}
return 0;
}
For compiling use -lutil .
While running a new tty device appears in /dev/pts .
vim accepts it as a terminal.

Can't invoke g++ with redirected stdout

I'm looking to invoke g++ and get the output. Here's my code:
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <iostream>
#include <boost/optional.hpp>
#include <vector>
#include <string>
namespace Util
{
template<typename T>
using optional = boost::optional<T>;
}
namespace Wide
{
namespace Driver
{
struct ProcessResult
{
std::string std_out;
int exitcode;
};
ProcessResult StartAndWaitForProcess(std::string name, std::vector<std::string> args, Util::optional<unsigned> timeout);
}
}
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <iostream>
#include <fcntl.h>
Wide::Driver::ProcessResult Wide::Driver::StartAndWaitForProcess(std::string name, std::vector<std::string> args, Util::optional<unsigned> timeout) {
int filedes[2];
pipe(filedes);
pid_t pid = fork();
if (pid == 0) {
while ((dup2(filedes[1], STDOUT_FILENO) == -1) && (errno == EINTR)) {}
auto fd = open("/dev/null", O_RDWR);
while ((dup2(fd, STDIN_FILENO) == -1) && (errno == EINTR)) {}
//freopen("/dev/null", "rw", stdin);
//freopen("/dev/null", "rw", stderr);
//close(filedes[1]);
close(filedes[0]);
std::vector<const char*> cargs;
cargs.push_back(name.c_str());
for (auto&& arg : args)
cargs.push_back(arg.c_str());
cargs.push_back(nullptr);
execv(name.c_str(), const_cast<char* const*>(&cargs[0]));
}
std::string std_out;
close(filedes[1]);
char buffer[4096];
while (1) {
ssize_t count = read(filedes[0], buffer, sizeof(buffer));
if (count == -1) {
if (errno == EINTR) {
continue;
} else {
perror("read");
exit(1);
}
} else if (count == 0) {
break;
} else {
std_out += std::string(buffer, buffer + count);
}
}
close(filedes[0]);
int status;
ProcessResult result;
result.std_out = std_out;
waitpid(pid, &status, 0);
if (!WIFEXITED(status))
result.exitcode = 1;
else {
result.exitcode = WEXITSTATUS(status);
if (result.exitcode != 0) {
std::cout << name << " failed with code " << result.exitcode << "\n";
}
}
return result;
}
int main()
{
auto r = Wide::Driver::StartAndWaitForProcess("g++", { "-std=c++14", "main.cpp" }, 150);
std::cout << r.std_out << "!!!!\n!!!!\n" << r.exitcode << "\n";
}
The output:
read: Bad file descriptor
g++ failed with code 1
!!!!
!!!!
1
Just invoke g++ main.cpp -std=c++14 && ./a.out.
I've used strace but it doesn't really give any more interesting details- the process runs, then fork/exec, then the above error. I can invoke other processes with the above code so I don't know what's so different about g++. I can invoke GCC with popen without problems so I don't know what's so different here.
The error here is really not very helpful. How can I invoke g++ and get the output?
The problem here is that you call execv which requires a full path to the executable as its first argument.
What you need is execvp which uses the contents of the PATH environment variable to find the executable, and thus only requires a name like g++.