The standard way would be the following:
if (ptrace(PTRACE_TRACEME, 0, NULL, 0) == -1)
printf("traced!\n");
In this case, ptrace returns an error if the current process is traced (e.g., running it with GDB or attaching to it).
But there is a serious problem with this: if the call returns successfully, GDB may not attach to it later. Which is a problem since I'm not trying to implement anti-debug stuff. My purpose is to emit an 'int 3' when a condition is met (e.g., an assert fails) and GDB is running (otherwise I get a SIGTRAP which stops the application).
Disabling SIGTRAP and emitting an 'int 3' every time is not a good solution because the application I'm testing might be using SIGTRAP for some other purpose (in which case I'm still screwed, so it wouldn't matter, but it's the principle of the thing :))
On Windows there is an API, IsDebuggerPresent, to check if process is under debugging. At Linux, we can check this with another way (not so efficient).
Check "/proc/self/status" for "TracerPid" attribute.
Example code:
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <ctype.h>
bool debuggerIsAttached()
{
char buf[4096];
const int status_fd = open("/proc/self/status", O_RDONLY);
if (status_fd == -1)
return false;
const ssize_t num_read = read(status_fd, buf, sizeof(buf) - 1);
close(status_fd);
if (num_read <= 0)
return false;
buf[num_read] = '\0';
constexpr char tracerPidString[] = "TracerPid:";
const auto tracer_pid_ptr = strstr(buf, tracerPidString);
if (!tracer_pid_ptr)
return false;
for (const char* characterPtr = tracer_pid_ptr + sizeof(tracerPidString) - 1; characterPtr <= buf + num_read; ++characterPtr)
{
if (isspace(*characterPtr))
continue;
else
return isdigit(*characterPtr) != 0 && *characterPtr != '0';
}
return false;
}
The code I ended up using was the following:
int
gdb_check()
{
int pid = fork();
int status;
int res;
if (pid == -1)
{
perror("fork");
return -1;
}
if (pid == 0)
{
int ppid = getppid();
/* Child */
if (ptrace(PTRACE_ATTACH, ppid, NULL, NULL) == 0)
{
/* Wait for the parent to stop and continue it */
waitpid(ppid, NULL, 0);
ptrace(PTRACE_CONT, NULL, NULL);
/* Detach */
ptrace(PTRACE_DETACH, getppid(), NULL, NULL);
/* We were the tracers, so gdb is not present */
res = 0;
}
else
{
/* Trace failed so GDB is present */
res = 1;
}
exit(res);
}
else
{
waitpid(pid, &status, 0);
res = WEXITSTATUS(status);
}
return res;
}
A few things:
When ptrace(PTRACE_ATTACH, ...) is successful, the traced process will stop and has to be continued.
This also works when GDB is attaching later.
A drawback is that when used frequently, it will cause a serious slowdown.
Also, this solution is only confirmed to work on Linux. As the comments mentioned, it won't work on BSD.
You could fork a child which would try to PTRACE_ATTACH its parent (and then detach if necessary) and communicates the result back. It does seem a bit inelegant though.
As you mention, this is quite costly. I guess it's not too bad if assertions fail irregularly. Perhaps it'd be worthwhile keeping a single long-running child around to do this - share two pipes between the parent and the child, child does its check when it reads a byte and then sends a byte back with the status.
I had a similar need, and came up with the following alternatives
static int _debugger_present = -1;
static void _sigtrap_handler(int signum)
{
_debugger_present = 0;
signal(SIGTRAP, SIG_DFL);
}
void debug_break(void)
{
if (-1 == _debugger_present) {
_debugger_present = 1;
signal(SIGTRAP, _sigtrap_handler);
raise(SIGTRAP);
}
}
If called, the debug_break function will only interrupt if a debugger is attached.
If you are running on x86 and want a breakpoint which interrupts in the caller (not in raise), just include the following header, and use the debug_break macro:
#ifndef BREAK_H
#define BREAK_H
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
int _debugger_present = -1;
static void _sigtrap_handler(int signum)
{
_debugger_present = 0;
signal(SIGTRAP, SIG_DFL);
}
#define debug_break() \
do { \
if (-1 == _debugger_present) { \
_debugger_present = 1; \
signal(SIGTRAP, _sigtrap_handler); \
__asm__("int3"); \
} \
} while(0)
#endif
I found that a modified version of the file descriptor "hack" described by Silviocesare and blogged by xorl worked well for me.
This is the modified code I use:
#include <stdio.h>
#include <unistd.h>
// gdb apparently opens FD(s) 3,4,5 (whereas a typical prog uses only stdin=0, stdout=1,stderr=2)
int detect_gdb(void)
{
int rc = 0;
FILE *fd = fopen("/tmp", "r");
if (fileno(fd) > 5)
{
rc = 1;
}
fclose(fd);
return rc;
}
If you just want to know whether the application is running under GDB for debugging purposes, the simplest solution on Linux is to readlink("/proc/<ppid>/exe"), and search the result for "gdb".
This is similar to terminus' answer, but uses pipes for communication:
#include <unistd.h>
#include <stdint.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#if !defined(PTRACE_ATTACH) && defined(PT_ATTACH)
# define PTRACE_ATTACH PT_ATTACH
#endif
#if !defined(PTRACE_DETACH) && defined(PT_DETACH)
# define PTRACE_DETACH PT_DETACH
#endif
#ifdef __linux__
# define _PTRACE(_x, _y) ptrace(_x, _y, NULL, NULL)
#else
# define _PTRACE(_x, _y) ptrace(_x, _y, NULL, 0)
#endif
/** Determine if we're running under a debugger by attempting to attach using pattach
*
* #return 0 if we're not, 1 if we are, -1 if we can't tell.
*/
static int debugger_attached(void)
{
int pid;
int from_child[2] = {-1, -1};
if (pipe(from_child) < 0) {
fprintf(stderr, "Debugger check failed: Error opening internal pipe: %s", syserror(errno));
return -1;
}
pid = fork();
if (pid == -1) {
fprintf(stderr, "Debugger check failed: Error forking: %s", syserror(errno));
return -1;
}
/* Child */
if (pid == 0) {
uint8_t ret = 0;
int ppid = getppid();
/* Close parent's side */
close(from_child[0]);
if (_PTRACE(PTRACE_ATTACH, ppid) == 0) {
/* Wait for the parent to stop */
waitpid(ppid, NULL, 0);
/* Tell the parent what happened */
write(from_child[1], &ret, sizeof(ret));
/* Detach */
_PTRACE(PTRACE_DETACH, ppid);
exit(0);
}
ret = 1;
/* Tell the parent what happened */
write(from_child[1], &ret, sizeof(ret));
exit(0);
/* Parent */
} else {
uint8_t ret = -1;
/*
* The child writes a 1 if pattach failed else 0.
*
* This read may be interrupted by pattach,
* which is why we need the loop.
*/
while ((read(from_child[0], &ret, sizeof(ret)) < 0) && (errno == EINTR));
/* Ret not updated */
if (ret < 0) {
fprintf(stderr, "Debugger check failed: Error getting status from child: %s", syserror(errno));
}
/* Close the pipes here, to avoid races with pattach (if we did it above) */
close(from_child[1]);
close(from_child[0]);
/* Collect the status of the child */
waitpid(pid, NULL, 0);
return ret;
}
}
Trying the original code under OS X, I found waitpid (in the parent) would always return -1 with an EINTR (System call interrupted). This was caused by pattach, attaching to the parent and interrupting the call.
It wasn't clear whether it was safe to just call waitpid again (that seemed like it might behave incorrectly in some situations), so I just used a pipe to do the communication instead. It's a bit of extra code, but will probably work reliably across more platforms.
This code has been tested on OS X v10.9.3 (Mavericks), Ubuntu 14.04 (Trusty Tahr) (3.13.0-24-generic) and FreeBSD 10.0.
For Linux, which implements process capabilities, this method will only work if the process has the CAP_SYS_PTRACE capability, which is typically set when the process is run as root.
Other utilities (gdb and lldb) also have this capability set as part of their filesystem metadata.
You can detect whether the process has effective CAP_SYS_PTRACE by linking against -lcap,
#include <sys/capability.h>
cap_flag_value_t value;
cap_t current;
/*
* If we're running under Linux, we first need to check if we have
* permission to to ptrace. We do that using the capabilities
* functions.
*/
current = cap_get_proc();
if (!current) {
fprintf(stderr, "Failed getting process capabilities: %s\n", syserror(errno));
return -1;
}
if (cap_get_flag(current, CAP_SYS_PTRACE, CAP_PERMITTED, &value) < 0) {
fprintf(stderr, "Failed getting permitted ptrace capability state: %s\n", syserror(errno));
cap_free(current);
return -1;
}
if ((value == CAP_SET) && (cap_get_flag(current, CAP_SYS_PTRACE, CAP_EFFECTIVE, &value) < 0)) {
fprintf(stderr, "Failed getting effective ptrace capability state: %s\n", syserror(errno));
cap_free(current);
return -1;
}
C++ version of Sam Liao's answer (Linux only):
// Detect if the application is running inside a debugger.
bool being_traced()
{
std::ifstream sf("/proc/self/status");
std::string s;
while (sf >> s)
{
if (s == "TracerPid:")
{
int pid;
sf >> pid;
return pid != 0;
}
std::getline(sf, s);
}
return false;
}
Related
I have a writer process which outputs its status at regular intervals as a readable chunck of wchar_t.
I would need to ensure the following properties:
When there's and update, the readers shouldn't read partial/corrupted data
The file should be volatile in memory so that when the writer quits, the file is gone
The file content size is variable
Multiple readers could read the file in parallel, doesn't matter if the content is synced, as long as is non partial for each client
If using truncate and then write, clients should only read the full file and not observe such partial operations
How could I implement such /procfs-like file, outside /procfs filesystem?
I was thinking to use classic c Linux file APIs and create something under /dev/shm by default, but I find it hard to implement effectively point 1 and 5 most of all.
How could I expose such file?
Typical solution is to create a new file in the same directory, then rename (hardlink) it over the old one.
This way, processes see either an old one or a new one, never a mix; and it only depends on the moment when they open the file.
The Linux kernel takes care of the caching, so if the file is accessed often, it will be in RAM (page cache). The writer must, however, remember to delete the file when it exits.
A better approach is to use fcntl()-based advisory record locks (typically over the entire file, i.e. .l_whence = SEEK_SET, .l_start = 0, .l_len = 0).
The writer will grab a write/exclusive lock before truncating and rewriting the contents, and readers a read/shared lock before reading the contents.
This requires cooperation, however, and the writer must be prepared to not be able to lock (or grabbing the lock may take undefined amount of time).
A Linux-only scheme would be to use atomic replacement (via rename/hardlinking), and file leases.
(When the writer process has an exclusive lease on an open file, it gets a signal whenever another process wants to open that same file (inode, not file name). It has at least a few seconds to downgrade or release the lease, at which point the opener gets access to the contents.)
Basically, the writer process creates an empty status file, and obtains exclusive lease on it. Whenever the writer receives a signal that a reader wants to access the status file, it writes the current status to the file, releases the lease, creates a new empty file in the same directory (same mount suffices) as the status file, obtains an exclusive lease on that one, and renames/hardlinks it over the status file.
If the status file contents do not change all the time, only periodically, then the writer process creates an empty status file, and obtains exclusive lease on it. Whenever the writer receives a signal that a reader wants to access the (empty) status file, it writes the current status to the file, and releases the lease. Then, when the writer process' status is updated, and there is no lease yet, it creates a new empty file in the status file directory, takes an exclusive lease on it, and renames/hardlinks over the status file.
This way, the status file is always updated just before a reader opens it, and only then. If there are multiple readers at the same time, they can open the status file without interruption when the writer releases the lease.
It is important to note that the status information should be collected in a single structure or similar, so that writing it out to the status file is efficient. Leases are automatically broken if not released soon enough (but there are a few seconds at least to react), and the lease is on the inode – file contents – not the file name, so we still need the atomic replacement.
Here's a crude example implementation:
#define _POSIX_C_SOURCE 200809L
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdarg.h>
#include <inttypes.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
#include <signal.h>
#include <limits.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#define LEASE_SIGNAL (SIGRTMIN+0)
static pthread_mutex_t status_lock = PTHREAD_MUTEX_INITIALIZER;
static int status_changed = 0;
static size_t status_len = 0;
static char *status = NULL;
static pthread_t status_thread;
static char *status_newpath = NULL;
static char *status_path = NULL;
static int status_fd = -1;
static int status_errno = 0;
char *join2(const char *src1, const char *src2)
{
const size_t len1 = (src1) ? strlen(src1) : 0;
const size_t len2 = (src2) ? strlen(src2) : 0;
char *dst;
dst = malloc(len1 + len2 + 1);
if (!dst) {
errno = ENOMEM;
return NULL;
}
if (len1 > 0)
memcpy(dst, src1, len1);
if (len2 > 0)
memcpy(dst+len1, src2, len2);
dst[len1+len2] = '\0';
return dst;
}
static void *status_worker(void *payload __attribute__((unused)))
{
siginfo_t info;
sigset_t mask;
int err, num;
/* This thread blocks all signals except LEASE_SIGNAL. */
sigfillset(&mask);
sigdelset(&mask, LEASE_SIGNAL);
err = pthread_sigmask(SIG_BLOCK, &mask, NULL);
if (err)
return (void *)(intptr_t)err;
/* Mask for LEASE_SIGNAL. */
sigemptyset(&mask);
sigaddset(&mask, LEASE_SIGNAL);
/* This thread can be canceled at any cancellation point. */
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
while (1) {
num = sigwaitinfo(&mask, &info);
if (num == -1 && errno != EINTR)
return (void *)(intptr_t)errno;
/* Ignore all but the lease signals related to the status file. */
if (num != LEASE_SIGNAL || info.si_signo != LEASE_SIGNAL || info.si_fd != status_fd)
continue;
/* We can be canceled at this point safely. */
pthread_testcancel();
/* Block cancelability for a sec, so that we maintain the mutex correctly. */
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
pthread_mutex_lock(&status_lock);
status_changed = 0;
/* Write the new status to the file. */
if (status && status_len > 0) {
const char *ptr = status;
const char *const end = status + status_len;
ssize_t n;
while (ptr < end) {
n = write(status_fd, ptr, (size_t)(end - ptr));
if (n > 0) {
ptr += n;
} else
if (n != -1) {
if (!status_errno)
status_errno = EIO;
break;
} else
if (errno != EINTR) {
if (!status_errno)
status_errno = errno;
break;
}
}
}
/* Close and release lease. */
close(status_fd);
status_fd = -1;
/* After we release the mutex, we can be safely canceled again. */
pthread_mutex_unlock(&status_lock);
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
pthread_testcancel();
}
}
static int start_status_worker(void)
{
sigset_t mask;
int result;
pthread_attr_t attrs;
/* This thread should block LEASE_SIGNAL signals. */
sigemptyset(&mask);
sigaddset(&mask, LEASE_SIGNAL);
result = pthread_sigmask(SIG_BLOCK, &mask, NULL);
if (result)
return errno = result;
/* Create the worker thread. */
pthread_attr_init(&attrs);
pthread_attr_setstacksize(&attrs, 2*PTHREAD_STACK_MIN);
result = pthread_create(&status_thread, &attrs, status_worker, NULL);
pthread_attr_destroy(&attrs);
/* Ready. */
return 0;
}
int set_status(const char *format, ...)
{
va_list args;
char *new_status = NULL;
int len;
if (!format)
return errno = EINVAL;
va_start(args, format);
len = vasprintf(&new_status, format, args);
va_end(args);
if (len < 0)
return errno = EINVAL;
pthread_mutex_lock(&status_lock);
free(status);
status = new_status;
status_len = len;
status_changed++;
/* Do we already have a status file prepared? */
if (status_fd != -1 || !status_newpath) {
pthread_mutex_unlock(&status_lock);
return 0;
}
/* Prepare the status file. */
do {
status_fd = open(status_newpath, O_WRONLY | O_CREAT | O_CLOEXEC, 0666);
} while (status_fd == -1 && errno == EINTR);
if (status_fd == -1) {
pthread_mutex_unlock(&status_lock);
return 0;
}
/* In case of failure, do cleanup. */
do {
/* Set lease signal. */
if (fcntl(status_fd, F_SETSIG, LEASE_SIGNAL) == -1)
break;
/* Get exclusive lease on the status file. */
if (fcntl(status_fd, F_SETLEASE, F_WRLCK) == -1)
break;
/* Replace status file with the new, leased one. */
if (rename(status_newpath, status_path) == -1)
break;
/* Success. */
pthread_mutex_unlock(&status_lock);
return 0;
} while (0);
if (status_fd != -1) {
close(status_fd);
status_fd = -1;
}
unlink(status_newpath);
pthread_mutex_unlock(&status_lock);
return 0;
}
int main(int argc, char *argv[])
{
char *line = NULL;
size_t size = 0;
ssize_t len;
if (argc != 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
const char *argv0 = (argc > 0 && argv[0]) ? argv[0] : "(this)";
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv0);
fprintf(stderr, " %s STATUS-FILE\n", argv0);
fprintf(stderr, "\n");
fprintf(stderr, "This program maintains a pseudofile-like status file,\n");
fprintf(stderr, "using the contents from standard input.\n");
fprintf(stderr, "Supply an empty line to exit.\n");
fprintf(stderr, "\n");
return EXIT_FAILURE;
}
status_path = join2(argv[1], "");
status_newpath = join2(argv[1], ".new");
unlink(status_path);
unlink(status_newpath);
if (start_status_worker()) {
fprintf(stderr, "Cannot start status worker thread: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
if (set_status("Empty\n")) {
fprintf(stderr, "Cannot create initial empty status: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
while (1) {
len = getline(&line, &size, stdin);
if (len < 1)
break;
line[strcspn(line, "\n")] = '\0';
if (line[0] == '\0')
break;
set_status("%s\n", line);
}
pthread_cancel(status_thread);
pthread_join(status_thread, NULL);
if (status_fd != -1)
close(status_fd);
unlink(status_path);
unlink(status_newpath);
return EXIT_SUCCESS;
}
Save the above as server.c, then compile using e.g.
gcc -Wall -Wextra -O2 server.c -lpthread -o server
This implements a status server, storing each line from standard input to the status file if necessary. Supply an empty line to exit. For example, to use the file status in the current directory, just run
./server status
Then, if you use another terminal window to examine the directory, you see it has a file named status (with typically zero size). But, cat status shows you its contents; just like procfs/sysfs pseudofiles.
Note that the status file is only updated if necessary, and only for the first reader/accessor after status changes. This keeps writer/server overhead and I/O low, even if the status changes very often.
The above example program uses a worker thread to catch the lease-break signals. This is because pthread mutexes cannot be locked or released safely in a signal handler (pthread_mutex_lock() etc. are not async-signal safe). The worker thread maintains its cancelability, so that it won't be canceled when it holds the mutex; if canceled during that time, it will be canceled after it releases the mutex. It is careful that way.
Also, the temporary replacement file is not random, it is just the status file name with .new appended at end. Anywhere on the same mount would work fine.
As long as other threads also block the lease break signal, this works fine in multithreaded programs, too. (If you create other threads after the worker thread, they'll inherit the correct signal mask from the main thread; start_status_worker() sets the signal mask for the calling thread.)
I do trust the approach in the program, but there may be bugs (and perhaps even thinkos) in this implementation. If you find any, please comment or edit.
I am trying to run the same program in two linux namespace.
The program needs to read and write the file /tmp/server.log.
So I want to make sure that program A read/write server.log, but actually it reads and writes /tmp/server-A.log. And for program B read/write server.log, it is actually reading and writing /tmp/server-B.log.
I try to use mount but not succeeded ... Can anyone help me? Or is there another way for me to provide file isolation so that the two programs will not actually read/write the same file?
#define _GNU_SOURCE
#include<sched.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
static int child_func(void* arg) {
system("mount --bind /tmp ./a");
FILE* file;
file = fopen("/tmp/server.log","rw");
// write some log ...
return 0;
}
static int child2_func(void* arg) {
system("mount --bind /tmp ./b");
file = fopen("/tmp/server.log","rw");
// write some log....
return 0;
}
int main(int argc, char** argv) {
// Allocate stack for child task.
const int STACK_SIZE = 1 * 1024 * 1024;
char* stack = malloc(STACK_SIZE);
char* stack2 = malloc(STACK_SIZE);
if (!stack || !stack2) {
perror("malloc");
exit(1);
}
pid_t pid,pid2;
if ((pid = clone(child_func, stack + STACK_SIZE, CLONE_NEWPID | CLONE_NEWUTS | CLONE_NEWNS | CLONE_NEWNET | SIGCHLD, NULL)) == -1) {
perror("clone");
exit(1);
}
if ((pid2 = clone(child2_func, stack2 + STACK_SIZE, CLONE_NEWPID | CLONE_NEWUTS | CLONE_NEWNS | CLONE_NEWNET | SIGCHLD, NULL)) == -1) {
perror("clone");
exit(1);
}
waitpid(pid,NULL,0);
waitpid(pid2,NULL,0);
return 0;
}
Update: I solve the problem based on the solutions answered below! Their solutions really help me!
You want something like this:
#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/mount.h>
#include <sys/wait.h>
#include <unistd.h>
int doChild(const char *source) {
if(unshare(CLONE_NEWNS)) {
perror("unshare");
return 1;
}
if(mount("none", "/", NULL, MS_REC|MS_PRIVATE, NULL)) {
perror("mount");
return 1;
}
if(mount(source, "/tmp/server.log", NULL, MS_BIND, NULL)) {
perror("mount");
return 1;
}
execlp("myunmodifiablepythonscript", "myunmodifiablepythonscript", (char*)NULL);
perror("execlp");
return 1;
}
int main(void) {
pid_t pidA, pidB;
pidA = fork();
if(pidA < 0) {
perror("fork");
return 1;
} else if(pidA == 0) {
return doChild("/tmp/server-A.log");
}
pidB = fork();
if(pidB < 0) {
perror("fork");
/* n.b.: pidA will still be running as an orphan. */
return 1;
} else if(pidB == 0) {
return doChild("/tmp/server-B.log");
}
waitpid(pidA, NULL, 0);
/* n.b.: if pidB finishes first, it will be a zombie until pidA finishes. */
waitpid(pidB, NULL, 0);
return 0;
}
A few notes:
Using clone correctly (which you weren't) is a pain. It's much easier to just use fork and then unshare after.
systemd stupidly makes mounts shared by default, which basically makes mount namespaces do nothing (i.e., changes will propagate back to other namespaces, thus defeating the purpose of a private namespace). mount("none", "/", NULL, MS_REC|MS_PRIVATE, NULL) undoes that to make them actually work.
I'm not sure what you were trying to bind mount, but it was the wrong thing. The right thing is mounting the individual log to the shared name.
You need to remount root to private. Check this link.
When you do --bind, you need do it in reverse way.
static int child_func(void* arg) {
mount("/", "/", NULL, MS_PRIVATE, NULL);
mount("./a", "/tmp", NULL, MS_BIND, NULL);
FILE* file;
file = fopen("/tmp/server.log","w");
return 0;
}
static int child_func(void* arg) {
mount("/", "/", NULL, MS_PRIVATE, NULL);
mount("./b", "/tmp", NULL, MS_BIND, NULL);
FILE* file;
file = fopen("/tmp/server.log","w");
return 0;
}
Suppose I create a named pipe on a Linux system:
$ mkfifo my_pipe
The next thing I want to do is write a little monitor program which tries to read() from my_pipe, but times out after a while. In the following pseudo-code, I have used a fictional function wait_for_avail(fd, timeout_ms):
int fd = open("my_pipe", O_RDONLY);
while (1) {
//Fictional wait_for_avail(fd, timeout_ms). Is there a real function
//that has this behaviour?
int rc = wait_for_avail(fd, 500);
if (rc == 1) {
char buf[64];
read(fd, buf, 64);
//do something with buf
} else {
fprintf(stderr, "Timed out while reading from my_pipe\n");
//do something else in the program
}
}
I thought poll with the POLLIN flag might work, but it does not. From my simple trials, I have found that it simply waits until another process has opened the named pipe for writing (but not for data to be available, i.e. read() would not block). By the way, for some reason, poll ignores your timeout and just seems to block forever until another process opens the pipe.
The only other solution I can think of is to open() the file with O_NONBLOCK, and sort of manually watch the time going by as I constantly try read()ing with a count of 0 bytes.
Is there a better solution out there?
EDIT: The process I have here blocks on opening the named pipe. However, if you use the O_NONBLOCK flag, the file opens right away. At that point, poll() can be used to wait (with an optional timeout) for the other end of the pipe to be opened for writing.
However, this still does have the behaviour of implementing a timeout for the read() function. It still appears to block as soon as you call read() (even if the pipe was opened with O_NONBLOCK)
Your idea about opening the fifo in non-blocking mode is correct. If you do that, poll()/select()/etc. can be used to wait for the other end to be opened, or timeout first.
The following example program just runs in an infinite loop waiting for other programs to write to my_pipe and echos the written text, with the occasional status update when there's no data or writer:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <poll.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void) {
while (1) {
int fd = open("my_pipe", O_RDONLY | O_NONBLOCK);
if (fd < 0) {
perror("open");
return EXIT_FAILURE;
}
struct pollfd waiter = {.fd = fd, .events = POLLIN};
while (1) {
// 10 second timeout
switch (poll(&waiter, 1, 10 * 1000)) {
case 0:
puts("The fifo timed out.");
break;
case 1:
if (waiter.revents & POLLIN) {
char buffer[BUFSIZ];
ssize_t len = read(fd, buffer, sizeof buffer - 1);
if (len < 0) {
perror("read");
return EXIT_FAILURE;
}
buffer[len] = '\0';
printf("Read: %s\n", buffer);
} else if (waiter.revents & POLLERR) {
puts("Got a POLLERR");
return EXIT_FAILURE;
} else if (waiter.revents & POLLHUP) {
// Writer closed its end
goto closed;
}
break;
default:
perror("poll");
return EXIT_FAILURE;
}
}
closed:
if (close(fd) < 0) {
perror("close");
return EXIT_FAILURE;
}
}
}
After a lot of help and patience from #Shawn, I managed to come up with an answer I found satisfying. Here are the contents of a file called pipe_watcher.c:
#include <stdio.h> //printf etc.
#include <errno.h> //errno
#include <string.h> //perror
#include <signal.h> //SIGALRM, sigaction, sigset
#include <time.h> //timer_create, timer_settime
#include <fcntl.h> //open, O_RDONLY
#include <unistd.h> //close
/* This code demonstrates how you can monitor a named pipe with timeouts on the
* read() system call.
*
* Compile with:
*
* gcc -o pipe_watcher pipe_watcher.c -lrt
*
* And run with:
*
* ./pipe_watcher PIPE_FILENAME
*/
//Just needed a dummy handler
void sigalrm_handler(int s) {
return;
}
int main(int argc, char **argv) {
//Check input argument count
if (argc != 2) {
puts("Usage:\n");
puts("\t./pipe_watcher PIPE_FILENAME");
return -1;
}
//Create a timer object
timer_t clk;
int rc = timer_create(CLOCK_REALTIME, NULL, &clk);
if (rc < 0) {
perror("Could not create CLOCK_REALTIME timer");
return -1;
}
//Create some time values for use with timer_settime
struct itimerspec half_second = {
.it_interval = {.tv_sec = 0, .tv_nsec = 0},
.it_value = {.tv_sec = 0, .tv_nsec = 500000000}
};
struct itimerspec stop_timer = {
.it_interval = {.tv_sec = 0, .tv_nsec = 0},
.it_value = {.tv_sec = 0, .tv_nsec = 0}
};
//Set up SIGALRM handler
struct sigaction sigalrm_act = {
.sa_handler = sigalrm_handler,
.sa_flags = 0
};
sigemptyset(&sigalrm_act.sa_mask);
rc = sigaction(SIGALRM, &sigalrm_act, NULL);
if (rc < 0) {
perror("Could not register signal handler");
timer_delete(clk);
return -1;
}
//We deliberately omit O_NONBLOCK, since we want blocking behaviour on
//read(), and we're willing to tolerate dealing with the blocking open()
int fd = open(argv[1], O_RDONLY);
if (fd < 0) {
char msg[80];
sprintf(msg, "Could not open [%s]", argv[1]);
perror(msg);
timer_delete(clk);
return -1;
}
puts("File opened");
while (1) {
//Buffer to read() into
char buf[80];
int len;
//Set up a timer to interrupt the read() call after 0.5 seconds
timer_settime(clk, 0, &half_second, NULL);
//Issue read() system call
len = read(fd, buf, 80);
//Check for errors. The else-if checks for EOF
if (len < 0) {
if (errno == EINTR) {
//This means we got interrupted by the timer; we can keep going
fprintf(stderr, "Timeout, trying again\n");
continue;
} else {
//Something really bad happened. Time to quit.
perror("read() failed");
//No point waiting for the timer anymore
timer_settime(clk, 0, &stop_timer, NULL);
break;
}
} else if (len == 0) {
puts("Reached end of file");
break;
}
//No error or EOF; stop the timer and print the results
timer_settime(clk, 0, &stop_timer, NULL);
write(STDOUT_FILENO, buf, len);
}
//Cleanup after ourselves
timer_delete(clk);
close(fd);
return 0;
}
The technique is to set up a timer before a (blocking) read() call. Then, we can simply check the return value of read() to see if it was interrupted due to a timeout, if a general error occurred, if EOF was reached, or if it successfully read data.
There's only one snag: you can't open the file in non-blocking mode; this causes open() to block until another process opens the pipe for writing. However, in my application this is actually a desirable feature. You could also set up SIGALRM to enforce a timeout on the open(), or maybe do it in another thread.
In fact, this technique should work with any other system call, so I might put together a little helper library to make this pattern easier to use.
EDIT
One more thing: it is very important to not use the SA_RESTART flag when registering the signal handler. Otherwise, even if a system call is interrupted by a signal, Linux will try it again after the signal is handled.
I want to open an image, and in Windows I do:
#include <windows.h>
..
ShellExecute(NULL, "open", "https://gsamaras.files.wordpress.com/2018/11/chronosgod.png", NULL, NULL, SW_SHOWNORMAL);
I would like to use a Linux approach, where it's so much easier to run something on the fly. Example:
char s[100];
snprintf(s, sizeof s, "%s %s", "xdg-open", "https://gsamaras.files.wordpress.com/2018/11/chronosgod.png");
system(s);
In my Ubuntu, it works. However, when running that in Wandbox (Live Demo), or in any other online compiler, I would most likely get an error:
sh: 1: xdg-open: not found
despite the fact that these online compilers seem to live in Linux (checked). I don't expect the online compiler to open a browser for me, but I did expect the code to run without an error. Ah, and forget Mac (personal laptop, limiting my machines).
Since I have no other Linux machine to check, my question is: Can I expect that this code will work in most of the major Linux distributions?
Maybe the fact that it failed on online compilers is misleading.
PS: This is for my post on God of Time, so no worries about security.
Although Antti Haapala already completely answered the question, I thought some comments about the approach, and an example function making safe use trivial, might be useful.
xdg-open is part of desktop integration utilities from freedesktop.org, as part of the Portland project. One can expect them to be available on any computer running a desktop environment participating in freedesktop.org. This includes GNOME, KDE, and Xfce.
Simply put, this is the recommended way of opening a resource (be it a file or URL) when a desktop environment is in use, in whatever application the user prefers.
If there is no desktop environment in use, then there is no reason to expect xdg-open to be available either.
For Linux, I would suggest using a dedicated function, perhaps along the following lines. First, a couple of internal helper functions:
#define _POSIX_C_SOURCE 200809L
#define _GNU_SOURCE
//
// SPDX-License-Identifier: CC0-1.0
//
#include <stdlib.h>
#include <unistd.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <dirent.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
/* Number of bits in an unsigned long. */
#define ULONG_BITS (CHAR_BIT * sizeof (unsigned long))
/* Helper function to open /dev/null to a specific descriptor.
*/
static inline int devnullfd(const int fd)
{
int tempfd;
/* Sanity check. */
if (fd == -1)
return errno = EINVAL;
do {
tempfd = open("/dev/null", O_RDWR | O_NOCTTY);
} while (tempfd == -1 && errno == EINTR);
if (tempfd == -1)
return errno;
if (tempfd != fd) {
if (dup2(tempfd, fd) == -1) {
const int saved_errno = errno;
close(tempfd);
return errno = saved_errno;
}
if (close(tempfd) == -1)
return errno;
}
return 0;
}
/* Helper function to close all except small descriptors
specified in the mask. For obvious reasons, this is not
thread safe, and is only intended to be used in recently
forked child processes. */
static void closeall(const unsigned long mask)
{
DIR *dir;
struct dirent *ent;
int dfd;
dir = opendir("/proc/self/fd/");
if (!dir) {
/* Cannot list open descriptors. Just try and close all. */
const long fd_max = sysconf(_SC_OPEN_MAX);
long fd;
for (fd = 0; fd < ULONG_BITS; fd++)
if (!(mask & (1uL << fd)))
close(fd);
for (fd = ULONG_BITS; fd <= fd_max; fd++)
close(fd);
return;
}
dfd = dirfd(dir);
while ((ent = readdir(dir)))
if (ent->d_name[0] >= '0' && ent->d_name[0] <= '9') {
const char *p = &ent->d_name[1];
int fd = ent->d_name[0] - '0';
while (*p >= '0' && *p <= '9')
fd = (10 * fd) + *(p++) - '0';
if (*p)
continue;
if (fd == dfd)
continue;
if (fd < ULONG_MAX && (mask & (1uL << fd)))
continue;
close(fd);
}
closedir(dir);
}
closeall(0) tries hard to close all open file descriptors, and devnullfd(fd) tries to open fd to /dev/null. These are used to make sure that even if the user spoofs xdg-open, no file descriptors are leaked; only the file name or URL is passed.
On non-Linux POSIXy systems, you can replace them with something more suitable. On BSDs, use closefrom(), and handle the first ULONG_MAX descriptors in a loop.
The xdg_open(file-or-url) function itself is something along the lines of
/* Launch the user-preferred application to open a file or URL.
Returns 0 if success, an errno error code otherwise.
*/
int xdg_open(const char *file_or_url)
{
pid_t child, p;
int status;
/* Sanity check. */
if (!file_or_url || !*file_or_url)
return errno = EINVAL;
/* Fork the child process. */
child = fork();
if (child == -1)
return errno;
else
if (!child) {
/* Child process. */
uid_t uid = getuid(); /* Real, not effective, user. */
gid_t gid = getgid(); /* Real, not effective, group. */
/* Close all open file descriptors. */
closeall(0);
/* Redirect standard streams, if possible. */
devnullfd(STDIN_FILENO);
devnullfd(STDOUT_FILENO);
devnullfd(STDERR_FILENO);
/* Drop elevated privileges, if any. */
if (setresgid(gid, gid, gid) == -1 ||
setresuid(uid, uid, uid) == -1)
_Exit(98);
/* Have the child process execute in a new process group. */
setsid();
/* Execute xdg-open. */
execlp("xdg-open", "xdg-open", file_or_url, (char *)0);
/* Failed. xdg-open uses 0-5, we return 99. */
_Exit(99);
}
/* Reap the child. */
do {
status = 0;
p = waitpid(child, &status, 0);
} while (p == -1 && errno == EINTR);
if (p == -1)
return errno;
if (!WIFEXITED(status)) {
/* Killed by a signal. Best we can do is I/O error, I think. */
return errno = EIO;
}
switch (WEXITSTATUS(status)) {
case 0: /* No error. */
return errno = 0; /* It is unusual, but robust to explicitly clear errno. */
case 1: /* Error in command line syntax. */
return errno = EINVAL; /* Invalid argument */
case 2: /* File does not exist. */
return errno = ENOENT; /* No such file or directory */
case 3: /* A required tool could not be found. */
return errno = ENOSYS; /* Not implemented */
case 4: /* Action failed. */
return errno = EPROTO; /* Protocol error */
case 98: /* Identity shenanigans. */
return errno = EACCES; /* Permission denied */
case 99: /* xdg-open does not exist. */
return errno = ENOPKG; /* Package not installed */
default:
/* None of the other values should occur. */
return errno = ENOSYS; /* Not implemented */
}
}
As already mentioned, it tries hard to close all open file descriptors, redirects the standard streams to /dev/null, ensures the effective and real identity matches (in case this is used in a setuid binary), and passes success/failure using the child process exit status.
The setresuid() and setresgid() calls are only available on OSes that have saved user and group ids. On others, use seteuid(uid) and setegid() instead.
This implementation tries to balance user configurability with security. Users can set the PATH so that their favourite xdg-open gets executed, but the function tries to ensure that no sensitive information or privileges are leaked to that process.
(Environment variables could be filtered, but they should not contain sensitive information in the first place, and we don't really know which ones a desktop environment uses. So better not mess with them, to keep user surprises to a minimum.)
As a minimal test main(), try the following:
int main(int argc, char *argv[])
{
int arg, status;
if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
fprintf(stderr, " %s FILE-OR-URL ...\n", argv[0]);
fprintf(stderr, "\n");
fprintf(stderr, "This example program opens each specified file or URL\n");
fprintf(stderr, "xdg-open(1), and outputs success or failure for each.\n");
fprintf(stderr, "\n");
return EXIT_SUCCESS;
}
status = EXIT_SUCCESS;
for (arg = 1; arg < argc; arg++)
if (xdg_open(argv[arg])) {
printf("%s: %s.\n", argv[arg], strerror(errno));
status = EXIT_FAILURE;
} else
printf("%s: Opened.\n", argv[arg]);
return status;
}
As the SPDX license identifier states, this example code is licensed under Creative Commons Zero 1.0. Use it any way you wish, in any code you want.
The xdg-open is part of the xdg-utils. They're almost always installed with the GUI desktop of any Linux distribution.
A Linux distribution can be installed without any Graphical User Interface, on servers say, and most probably then they would lack xdg-open.
Instead of system, you could - and should - use fork + exec - if exec fails then xdg-open could not be executed.
The online compilers most probably don't have any Desktop GUI installed on them, thus the lack of that utility.
I am writing a small http proxy server(in C) on a linux machine, Ubuntu 18.04.1 to be specific, and I've been trying to find a way to get the pid of the process that is connecting to it.
It might be of use to mention that the proxy is intended to proxy connections only for processes running on the same machine, so I guess this should make this task possible.
The server uses AF_INET family sockets along with read/write operations in order to do it's job; I am mentioning this because after some research I did encounter threads about "ancillary data",for example: Is there a way to get the uid of the other end of a unix socket connection
Ancillary data contain credentials of the connecting socket(such as PID), but only work on AF_UNIX sockets, used for local IPC, and requires us to explicitly send/receive it on both sides(client/server). In my case, although, as I mentioned, the server will only proxy traffic on the same machine as the server, I need to use AF_INET sockets, so everyone(e.g. web browser) is able to connect to it.
Performance is not so critical; so any suggestions(including workarounds using system calls etc.) are very welcome.
We can use netstat -nptW output to see which local processes' TCP connections. As the output may be security sensitive, superuser privileges are required to see processes belonging to all users.
Since there is no reason to run a proxy service with elevated privileges (expect perhaps CAP_NET_BIND_SERVICE), a privileged helper program is needed.
I pondered a suitable security model for a bit, and came to the conclusion that a helper which examines the connected socket given to it (as say standard input), and outputs just the peer PID(s), would be safest: it would be extremely hard to misuse it, and even if possible, only the peer process ID is revealed.
Here is the example helper, tcp-peer-pids.c:
#define _POSIX_C_SOURCE 200809L
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netdb.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#define EXITCODE_OK 0
#define EXITCODE_STDIN_INVALID 1
#define EXITCODE_UNKNOWN_ADDRESS 2
#define EXITCODE_NETSTAT 3
#define EXITCODE_NETSTAT_OUTPUT 4
#define EXITCODE_WRITE_ERROR 5
#define EXITCODE_PRIVILEGES 6
static pid_t *pids = NULL;
static size_t num_pids = 0;
static size_t max_pids = 0;
static int add_pid(const pid_t p)
{
size_t i;
/* Check if already listed. */
for (i = 0; i < num_pids; i++)
if (pids[i] == p)
return 0;
/* Ensure enough room in pids array. */
if (num_pids >= max_pids) {
const size_t max_temp = (num_pids | 1023) + 1025 - 8;
pid_t *temp;
temp = realloc(pids, max_temp * sizeof pids[0]);
if (!temp)
return ENOMEM;
pids = temp;
max_pids = max_temp;
}
pids[num_pids++] = p;
return 0;
}
int main(void)
{
struct sockaddr_storage sock_addr;
socklen_t sock_addrlen = sizeof sock_addr;
char sock_match[128], sock_host[64], sock_port[32];
struct sockaddr_storage peer_addr;
socklen_t peer_addrlen = sizeof peer_addr;
char peer_match[128], peer_host[64], peer_port[32];
FILE *cmd;
char *line = NULL;
size_t size = 0;
ssize_t len;
int status;
/* Socket address is *remote*, and peer address is *local*.
This is because the variables are named after their matching netstat lines. */
if (getsockname(STDIN_FILENO, (struct sockaddr *)&sock_addr, &sock_addrlen) == -1) {
fprintf(stderr, "Standard input is not a valid socket.\n");
exit(EXITCODE_STDIN_INVALID);
}
if (getpeername(STDIN_FILENO, (struct sockaddr *)&peer_addr, &peer_addrlen) == -1) {
fprintf(stderr, "Standard input is not a connected socket.\n");
exit(EXITCODE_STDIN_INVALID);
}
if ((sock_addr.ss_family != AF_INET && sock_addr.ss_family != AF_INET6) ||
(peer_addr.ss_family != AF_INET && peer_addr.ss_family != AF_INET6)) {
fprintf(stderr, "Standard input is not an IP socket.\n");
exit(EXITCODE_STDIN_INVALID);
}
/* For security, we close the standard input descriptor, */
close(STDIN_FILENO);
/* and redirect it from /dev/null, if possible. */
{
int fd = open("/dev/null", O_RDONLY);
if (fd != -1 && fd != STDIN_FILENO) {
dup2(fd, STDIN_FILENO);
close(fd);
}
}
/* Convert sockets to numerical host and port strings. */
if (getnameinfo((const struct sockaddr *)&sock_addr, sock_addrlen,
sock_host, sizeof sock_host, sock_port, sizeof sock_port,
NI_NUMERICHOST | NI_NUMERICSERV)) {
fprintf(stderr, "Unknown socket address.\n");
exit(EXITCODE_UNKNOWN_ADDRESS);
}
if (getnameinfo((const struct sockaddr *)&peer_addr, peer_addrlen,
peer_host, sizeof peer_host, peer_port, sizeof peer_port,
NI_NUMERICHOST | NI_NUMERICSERV)) {
fprintf(stderr, "Unknown peer address.\n");
exit(EXITCODE_UNKNOWN_ADDRESS);
}
/* Combine to the host:port format netstat uses. */
snprintf(sock_match, sizeof sock_match, "%s:%s", sock_host, sock_port);
snprintf(peer_match, sizeof peer_match, "%s:%s", peer_host, peer_port);
/* Switch to privileged user, if installed as setuid. */
{
uid_t real_uid = getuid();
gid_t real_gid = getgid();
uid_t effective_uid = geteuid();
gid_t effective_gid = getegid();
if (real_gid != effective_gid || real_uid != effective_uid) {
/* SetUID or SetGID in effect. Switch privileges. */
if (setresgid(effective_gid, effective_gid, effective_gid) == -1 ||
setresuid(effective_uid, effective_uid, effective_uid) == -1) {
fprintf(stderr, "Error in privileges: %s.\n", strerror(errno));
exit(EXITCODE_PRIVILEGES);
}
}
}
/* Run netstat to obtain the data; redirect standard error to standard output. */
cmd = popen("LANG=C LC_ALL=C /bin/netstat -nptW 2>&1", "r");
if (!cmd) {
fprintf(stderr, "Cannot run netstat.\n");
exit(EXITCODE_NETSTAT);
}
/* Input line loop. */
while (1) {
char *field[8], *ends;
long val;
pid_t p;
len = getline(&line, &size, cmd);
if (len < 1)
break;
/* Split each line into fields. */
field[0] = strtok(line, "\t\n\v\f\r "); /* Protocol */
/* We are only interested in tcp ("tcp" and "tcp6" protocols). */
if (strcmp(field[0], "tcp") && strcmp(field[0], "tcp6"))
continue;
field[1] = strtok(NULL, "\t\n\v\f\r "); /* Recv-Q */
field[2] = strtok(NULL, "\t\n\v\f\r "); /* Send-Q */
field[3] = strtok(NULL, "\t\n\v\f\r "); /* Local address (peer) */
field[4] = strtok(NULL, "\t\n\v\f\r "); /* Remote address (sock) */
field[5] = strtok(NULL, "\t\n\v\f\r "); /* State */
field[6] = strtok(NULL, "\t\n\v\f\r /"); /* PID */
field[7] = strtok(NULL, "\t\n\v\f\r "); /* Process name */
/* Local address must match peer_match, and foreign/remote sock_match. */
if (strcmp(field[3], peer_match) || strcmp(field[4], sock_match))
continue;
/* This line corresponds to the process we are looking for. */
/* Missing PID field is an error at this point. */
if (!field[6])
break;
/* Parse the PID. Parsing errors are fatal. */
ends = field[6];
errno = 0;
val = strtol(field[6], &ends, 10);
if (errno || ends == field[6] || *ends != '\0' || val < 1)
break;
p = (pid_t)val;
if ((long)p != val)
break;
/* Add the pid to the known pids list. */
if (add_pid(p))
break;
}
/* The line buffer is no longer needed. */
free(line);
/* I/O error? */
if (!feof(cmd) || ferror(cmd)) {
fprintf(stderr, "Error reading netstat output.\n");
exit(EXITCODE_NETSTAT_OUTPUT);
}
/* Reap the netstat process. */
status = pclose(cmd);
if (status == -1) {
fprintf(stderr, "Error reading netstat output: %s.\n", strerror(errno));
exit(EXITCODE_NETSTAT_OUTPUT);
}
if (!WIFEXITED(status)) {
fprintf(stderr, "Netstat died unexpectedly.\n");
exit(EXITCODE_NETSTAT_OUTPUT);
}
if (WEXITSTATUS(status)) {
fprintf(stderr, "Netstat failed with exit status %d.\n", WEXITSTATUS(status));
exit(EXITCODE_NETSTAT_OUTPUT);
}
/* Output the array of pids as binary data. */
if (num_pids > 0) {
const char *head = (const char *)pids;
const char *const ends = (const char *)(pids + num_pids);
ssize_t n;
while (head < ends) {
n = write(STDOUT_FILENO, head, (size_t)(ends - head));
if (n > 0)
head += n;
else
if (n != -1)
exit(EXITCODE_WRITE_ERROR);
else
if (errno != EINTR)
exit(EXITCODE_WRITE_ERROR);
}
}
/* Discard the pids array. */
free(pids);
exit(EXITCODE_OK);
}
It can be run using ordinary user privileges (in which case it'll only know about processes owned by that user), root privileges, or as setuid root.
If used with sudo, ensure you use rule proxyuser ALL = NOPASSWD: /path/to/helper, because sudo has no way of asking a password there. I would probably just install the helper as setuid root at /usr/lib/yourproxy/tcp-peer-pid, owner root, group your proxy service group, and no access to other users (root:proxygroup -r-sr-x---).
The helper is tightly coupled to netstat -nptW output format, but does explicitly set the C locale to avoid getting localized output.
The comparison address:port strings to match to "Local Address" and "Foreign Address" in netstat output are constructed from the addresses returned by getpeername() and getsockname(), respectively, using [getnameinfo()(http://man7.org/linux/man-pages/man3/getnameinfo.3.html) in numerical form (using NI_NUMERICHOST | NI_NUMERICSERV flags).
The helper provides the PIDs in binary form to the server, because the server code would have been too long to fit in a single post here otherwise.
Here is an example TCP service, server.c, which uses the above helper to find out the PID of the peer end of the socket on the local computer. (To avoid denial-of-service attacks, you should set an IP filter that rejects accesses to your proxy service port from outside the computer.)
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <netdb.h>
#include <signal.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#ifndef HELPER_PATH
#define HELPER_PATH "./tcp-peer-pids"
#endif
#ifndef HELPER_NAME
#define HELPER_NAME "tcp-peer-pids"
#endif
#ifndef SUDO_PATH
#define SUDO_PATH "/usr/bin/sudo"
#endif
#ifndef SUDO_NAME
#define SUDO_NAME "sudo"
#endif
/*
* Signal handler, to detect INT (Ctrl+C), HUP, and TERM signals.
*/
static volatile sig_atomic_t done = 0;
static void handle_done(int signum)
{
/* In Linux, all signals have signum > 0. */
__atomic_store_n(&done, (sig_atomic_t)signum, __ATOMIC_SEQ_CST);
}
static int install_done(int signum)
{
struct sigaction act;
memset(&act, 0, sizeof act);
sigemptyset(&act.sa_mask);
act.sa_flags = SA_RESTART; /* Do not interrupt slow syscalls. */
act.sa_handler = handle_done;
if (sigaction(signum, &act, NULL) == -1)
return -1; /* errno set by getpeername() */
return 0;
}
/* Helper function: Move descriptors away from STDIN/STDOUT/STDERR.
Returns 0 if successful, -1 with errno set if an error occurs. */
static inline int normalfds(int fd[], const size_t n)
{
unsigned int closemask = 0;
int err = 0;
size_t i;
int newfd;
for (i = 0; i < n; i++)
while (fd[i] == STDIN_FILENO || fd[i] == STDOUT_FILENO || fd[i] == STDERR_FILENO) {
newfd = dup(fd[i]);
if (newfd == -1) {
err = errno;
break;
}
closemask |= 1u << fd[i];
fd[i] = newfd;
}
/* Close temporary descriptors. */
if (closemask & (1u << STDIN_FILENO)) close(STDIN_FILENO);
if (closemask & (1u << STDOUT_FILENO)) close(STDOUT_FILENO);
if (closemask & (1u << STDERR_FILENO)) close(STDERR_FILENO);
/* Success? */
if (!err)
return 0;
/* Report error. */
errno = err;
return -1;
}
/* Return the number of peer processes.
If an error occurs, returns zero; examine errno. */
size_t peer_pids(const int connfd, pid_t *const pids, size_t maxpids)
{
char *in_data = NULL;
size_t in_size = 0;
size_t in_used = 0;
size_t n;
int binpipe[2], status;
pid_t child, p;
/* Sanity check. */
if (connfd == -1) {
errno = EBADF;
return 0;
}
/* Create a pipe to transfer the PIDs (in binary). */
if (pipe(binpipe) == -1)
return 0; /* errno set by pipe(). */
/* Make sure the binary pipe descriptors do not conflict with standard descriptors. */
if (normalfds(binpipe, 2) == -1) {
const int saved_errno = errno;
close(binpipe[0]);
close(binpipe[1]);
errno = saved_errno;
return 0;
}
/* Fork a child process. */
child = fork();
if (child == -1) {
const int saved_errno = errno;
close(binpipe[0]);
close(binpipe[1]);
errno = saved_errno;
return 0;
}
if (!child) {
/* This is the child process. */
#ifdef USE_SUDO
const char *cmd_path = SUDO_PATH;
char *const cmd_args[3] = { SUDO_NAME, HELPER_PATH, NULL };
#else
const char *cmd_path = HELPER_PATH;
char *const cmd_args[2] = { HELPER_NAME, NULL };
#endif
/* The child runs in its own process group, for easier management. */
setsid();
/* Close read end of pipe. */
close(binpipe[0]);
/* Move established connection to standard input. */
if (connfd != STDIN_FILENO) {
if (dup2(connfd, STDIN_FILENO) != STDIN_FILENO)
_Exit(99);
close(connfd);
}
/* Move write end of pipe to standard output. */
if (dup2(binpipe[1], STDOUT_FILENO) != STDOUT_FILENO)
_Exit(99);
else
close(binpipe[1]);
/* Execute helper. */
execv(cmd_path, cmd_args);
/* Failed to execute helper. */
_Exit(98);
}
/* Parent process. */
/* Close write end of pipe, so we detect when child exits. */
close(binpipe[1]);
/* Read all output from child. */
status = 0;
while (1) {
ssize_t bytes;
if (in_used >= in_size) {
const size_t size = (in_used | 1023) + 1025 - 8;
char *temp;
temp = realloc(in_data, in_size);
if (!temp) {
status = ENOMEM;
break;
}
in_data = temp;
in_size = size;
}
bytes = read(binpipe[0], in_data + in_used, in_size - in_used);
if (bytes > 0) {
in_used += bytes;
} else
if (bytes == 0) {
/* End of input condition. */
break;
} else
if (bytes != -1) {
status = EIO;
break;
} else
if (errno != EINTR) {
status = errno;
break;
}
}
/* Close the pipe. */
close(binpipe[0]);
/* Abort, if an error occurred. */
if (status) {
free(in_data);
kill(-child, SIGKILL);
do {
p = waitpid(child, NULL, 0);
} while (p == -1 && errno == EINTR);
errno = status;
return 0;
}
/* Reap the child process. */
do {
status = 0;
p = waitpid(child, &status, 0);
} while (p == -1 && errno == EINTR);
if (p == -1) {
const int saved_errno = errno;
free(in_data);
errno = saved_errno;
return 0;
}
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
free(in_data);
errno = ESRCH; /* The helper command failed, really. */
return 0;
}
/* We expect an integer number of pid_t's. Check. */
n = in_used / sizeof (pid_t);
if ((in_used % sizeof (pid_t)) != 0) {
free(in_data);
errno = EIO;
return 0;
}
/* None found? */
if (!n) {
free(in_data);
errno = ENOENT; /* Not found, really. */
return 0;
}
/* Be paranoid, and verify the pids look sane. */
{
const pid_t *const pid = (const pid_t *const)in_data;
size_t i;
for (i = 0; i < n; i++)
if (pid[i] < 2) {
free(in_data);
errno = ESRCH; /* Helper failed */
return 0;
}
}
/* Copy to user buffer, if specified. */
if (maxpids > n)
memcpy(pids, in_data, n * sizeof (pid_t));
else
if (maxpids > 0)
memcpy(pids, in_data, maxpids * sizeof (pid_t));
/* The pid buffer is no longer needed. */
free(in_data);
/* Return the number of pids we actually received. */
return n;
}
int main(int argc, char *argv[])
{
struct addrinfo hints, *list, *curr;
const char *node, *serv;
int service_fd, err;
struct sockaddr_storage client_addr;
socklen_t client_addrlen;
int client_fd;
if (argc != 3) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
fprintf(stderr, " %s HOST PORT\n", argv[0]);
fprintf(stderr, "\n");
return EXIT_FAILURE;
}
/* Install signal handers for Ctrl+C, HUP, and TERM. */
if (install_done(SIGINT) ||
install_done(SIGHUP) ||
install_done(SIGTERM)) {
fprintf(stderr, "Cannot install signal handlers: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
/* Empty or - or * is a wildcard host. */
if (argv[1][0] == '\0' || !strcmp(argv[1], "-") || !strcmp(argv[1], "*"))
node = NULL;
else
node = argv[1];
serv = argv[2];
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC; /* IPv4 or IPv6 */
hints.ai_socktype = SOCK_STREAM; /* TCP */
hints.ai_flags = AI_PASSIVE;
hints.ai_protocol = 0;
hints.ai_canonname = NULL;
hints.ai_addr = NULL;
hints.ai_next = NULL;
list = NULL;
err = getaddrinfo(node, serv, &hints, &list);
if (err) {
fprintf(stderr, "Invalid host and/or port: %s.\n", gai_strerror(err));
return EXIT_FAILURE;
}
service_fd = -1;
err = 0;
for (curr = list; curr != NULL; curr = curr->ai_next) {
service_fd = socket(curr->ai_family, curr->ai_socktype, curr->ai_protocol);
if (service_fd == -1)
continue;
errno = 0;
if (bind(service_fd, curr->ai_addr, curr->ai_addrlen) == -1) {
if (!err)
if (errno == EADDRINUSE || errno == EADDRNOTAVAIL || errno == EACCES)
err = errno;
close(service_fd);
service_fd = -1;
continue;
}
if (listen(service_fd, 5) == -1) {
if (!err)
if (errno == EADDRINUSE)
err = errno;
close(service_fd);
service_fd = -1;
continue;
}
/* This socket works. */
break;
}
freeaddrinfo(list);
list = curr = NULL;
if (service_fd == -1) {
if (err)
fprintf(stderr, "Cannot listen for incoming connections on the specified host and port: %s.\n", strerror(err));
else
fprintf(stderr, "Cannot listen for incoming connections on the specified host and port.\n");
return EXIT_FAILURE;
}
/* Do not leak the listening socket to child processes. */
fcntl(service_fd, F_SETFD, FD_CLOEXEC);
/* We also want the listening socket to be nonblocking. */
fcntl(service_fd, F_SETFL, O_NONBLOCK);
fprintf(stderr, "Process %ld is waiting for incoming TCP connections.\n", (long)getpid());
/* Incoming connection loop. */
while (!done) {
struct timeval t;
char client_host[64]; /* 64 for numeric, 1024 for non-numeric */
char client_port[32];
pid_t client_pid;
fd_set fds;
t.tv_sec = 0;
t.tv_usec = 100000; /* Max. 0.1s delay to react to done signal. */
FD_ZERO(&fds);
FD_SET(service_fd, &fds);
if (select(service_fd + 1, &fds, NULL, NULL, &t) < 1)
continue;
client_addrlen = sizeof client_addr;
client_fd = accept(service_fd, (struct sockaddr *)&client_addr, &client_addrlen);
if (client_fd == -1) {
if (errno == EINTR || errno == ECONNABORTED)
continue;
fprintf(stderr, "Error accepting an incoming connection: %s.\n", strerror(errno));
continue;
}
if (getnameinfo((const struct sockaddr *)&client_addr, client_addrlen,
client_host, sizeof client_host, client_port, sizeof client_port,
NI_NUMERICHOST | NI_NUMERICSERV) != 0) {
fprintf(stderr, "Cannot resolve peer address for incoming connection, so dropping it.\n");
close(client_fd);
continue;
}
printf("Incoming connection from %s:%s", client_host, client_port);
fflush(stdout);
if (peer_pids(client_fd, &client_pid, 1) != 1) {
printf(", but cannot determine process ID. Dropped.\n");
close(client_fd);
continue;
}
printf(" from local process %ld.\n", (long)client_pid);
fflush(stdout);
/*
* Handle connection.
*/
printf("Closing connection.\n");
fflush(stdout);
close(client_fd);
}
/* Close service socket. */
close(service_fd);
switch (__atomic_load_n(&done, __ATOMIC_SEQ_CST)) {
case SIGINT:
fprintf(stderr, "Received INT signal.\n");
break;
case SIGHUP:
fprintf(stderr, "Received HUP signal.\n");
break;
case SIGTERM:
fprintf(stderr, "Received TERM signal.\n");
break;
}
return EXIT_SUCCESS;
}
The peer_pids() function communicates with the helper process. It is very straightforward, albeit careful to not return unreliable data: instead of ignoring errors or trying to recover from them, it reports failure. This allows the main program do if (peer_pids(client_fd, &pid, 1) != 1) /* Don't know! */ and drop any connection the server is unsure of -- an approach I consider the sane one here.
The normalfds() helper function is often ignored. It helps avoid issues if any of the standard streams are/get closed. It simply moves the set of descriptors away from the three standard streams, using at most three extra descriptors.
You can define USE_SUDO at compile time to have it use sudo when executing the helper. Define HELPER_PATH and HELPER_NAME to the absolute path to the helper and its file name, respectively. (As it is now, they default to ./tcp-peer-pid and tcp-peer-pid, for easier testing.)
The server does install a signal handler for INT (Ctrl+C), HUP (sent when the user closes the terminal), or TERM signals, which all cause it to stop accepting new connections and exit in a controlled manner. (Because the signal handler is installed using SA_RESTART flag, its delivery will not interrupt slow syscalls or cause errno == EINTR. This also means that accept() should not block, or the signal delivery will not be noticed. So, blocking in select() for 0.1s, and checking if a signal was delivered in between, is a good compromise, at least in an example server.)
On my machine, I compiled and tested the service in one terminal window using
gcc -Wall -O2 tcp-peer-pids.c -o tcp-peer-pids
gcc -Wall -O2 "-DHELPER_PATH=\"$PWD/tcp-peer-pids\"" server.c -o server
./server - 2400
That will report Process # is waiting for incoming TCP connections. In another window, using Bash or POSIX shell, I run one or more test netcat commands:
nc localhost 2400 & wait
It might look silly to run a command in the background, and immediately wait for it, but that way you can see the PID of the nc process.
On my system, all loopback (127.x.y.z), TCP/IPv4, and TCP/IPv6 (the addresses of my ethernet and WiFi interfaces) worked fine, and reliably reported the correct PID of the process connecting to the example server.
There are a number of cases where the number of PIDs reported might vary: For example, if the program has executed a child process, but left the connected descriptor open in the child as well. (This should be considered a bug.) Another typical case is the program having exited before the netstat command executes.
If you find any typos or errors or strange behaviour, let me know in a comment so I can verify and fix. I wrote both programs in one sitting, so they are quite likely to contain bugs. As I mentioned, I would not trust either in production before having a colleague (or myself a few times, later on, with fresh eyes) going through it with a critical/paranoid eye.
I would personally only use this approach for logging and statistics, not access control per se. By access control, I mean that you should configure an IP filter (the firewall built in to the Linux kernel) to limit access to only trusted hosts; and specifically allow no incoming proxy connections to the proxy service if only local applications are to be proxied, rather than rely on this detecting all remote connections.
For application-specific logging/limiting, use readlink() on the /proc/PID/exe pseudosymlink. This cannot be faked, but the call may fail if the executable is not accessible, or is too deep in the directory tree. (In those cases I'd reject the proxy connection altogether.)
Note that it is usually trivial for an user to copy an executable to any directory they own, and execute it from there. This means that for application-specific limiting to work at all, you should have tight limits for all applications by default, and relax the limits for specific executables.