How to drop privilege temporarily from root? - c

I am developing a daemon running as root, but need to call an API with the user, I checked the API codes, it uses getuid() to get the user.
If root user drops privilege by setuid() , it can't be restored to root. If calling seteuid(), the API will still do something as user uid=0.
I think fork before accessing API and setuid in the child process should work, but even if COW , it will cost much if calling API many times. Is it possible to solve the problem except using process pool?

Yes! Create a single process to call the API with the appropriate UID and communicate with the rest of the program through a Pipe, a UNIX domain socket or (shared memory)1.
I mean, fork only once and keep the privileged user running another process. Then create communication between the two if needed and as needed. Also, you might want to consider using dbus since it also integrates perfectly with systemd and on modern linux you want your daemon to interact nicely with both.
Note: I am by no means an expert on the subject, but this is a simple idea that seems clear to me. You don't need to create a process for every call to the API. This is a good example of the XY problem, the real problem that you want to solve, has nothing to do with avoiding to fork() multiple times because the idea of doing that is the wrong solution. You only need to fork() once, drop privileges and stay there without privileges, communicating with the parent process if/as needed.
1Any IPC mechanism that works for you.

Just call seteuid(2) to do the appropiate unprivileged stuff. seteuid(2) allows to switch between the real(or saved) user id (the one that launches the suid program or root in your case) and the suid user id (the one the suid program belongs to) so there should be no problem to regain privileged user id afterwards (as the saved user id is root, you don't have any issue to switch to it again and again).
If you change uids with setuid(2) you'll change all (effective, saved and real uids) and this is only allowed to the root user (or a program setuid root, and there's no way back then).
Look at the next example:
File pru49015.c:
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
int main(int argc, char **argv)
{
int opt, suid = getuid(), /* this is the saved uid */
uid = 0;
while ((opt = getopt(argc, argv, "i:")) != EOF) {
switch (opt) {
case 'i': uid = atoi(optarg); break;
}
}
/* execute this program with root privileges, like setuid root, for example */
printf("real uid=%d; effective uid=%d\n", getuid(), geteuid());
seteuid(uid); /* change to the non-privileged id configured */
printf("real uid=%d; effective uid=%d\n", getuid(), geteuid());
seteuid(suid); /* return back to saved uid */
printf("real uid=%d; effective uid=%d\n", getuid(), geteuid());
}
You will get an output like this:
$ pru49015 -i 37
real uid=502; effective uid=0
real uid=502; effective uid=37
real uid=502; effective uid=502
when used as a setuid-root program
If you use it as root, you'll get the following output:
$ sudo pru$$ -i 37
real uid=0; effective uid=0
real uid=0; effective uid=37
real uid=0; effective uid=0
The mechanism is that you are allowed on a setuid- program to switch between the user you are (let's call it the saved user id) and the user the program runs setuid to (the called effective user id or suid user) as many times as you want.

You can store the former effective uid in the saved UID of the process:
uid_t real = getuid();
uid_t privileged = geteuid();
setresuid(real, real, privileged);
do_API_call(); // API's getuid() call now returns real
setresuid(real, privileged, -1); // allowed, since saved==privileged
There's a corresponding setresgid to use saved GIDs, too.
Note that this answer is specific to Linux (as per question tags). A similar call exists on HP-UX and some BSD systems, but I haven't checked that the semantics are identical.
Actually, on further reading setreuid() should be sufficient (and POSIX-conformant). setuid() says:
If the effective UID of the caller is root (more precisely: if the caller has the CAP_SETUID capability), the real UID and saved set-user-ID are also
set.
and
If the user is root or the program is set-user-ID-root, special care must be taken. The setuid() function checks the effective user ID of the caller and if it is the superuser, all process-related user ID's are
set to uid. After this has occurred, it is impossible for the program to regain root privileges.
but there is no such statement for setreuid().

From here:
Normally when a process is executed, the effective, real, and saved user and group IDs are all set to the real user and group ID of the process's parent, respectively. However, when the setuid bit is set on an executable, the effective and saved user IDs are set to the user ID that owns the file.

Related

Is there a mechanism for manipulating a process like prctl(), but for forked/exec*()d processes?

Is it possible to do the equivalent of prctl(PR_SET_DUMPABLE, 0), but for other process, e.g. a child process, or one that was exec'd? The program below demonstrates that the dumpable flag is not inherited by a process that was execv'd.
#include <stdio.h>
#include <stdlib.h>
#include <sys/prctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char **argv) {
struct stat st;
if (argc < 2) {
fprintf(stderr, "usage: %s PROGRAM [ARGS...]\n", argv[0]);
return EXIT_FAILURE;
}
if (stat("/proc/self/fd", &st)) {
perror("stat");
return EXIT_FAILURE;
}
printf("before: owner of /proc/self/fd: %d\n", st.st_uid);
if (prctl(PR_SET_DUMPABLE, 0) == -1) {
perror("prctl");
return EXIT_FAILURE;
}
if (stat("/proc/self/fd", &st)) {
perror("stat");
return EXIT_FAILURE;
}
printf("after: owner of /proc/self/fd: %d\n", st.st_uid);
if (execv(argv[1], argv + 1) == -1) {
perror("execv");
return EXIT_FAILURE;
}
}
$ ./a.out /usr/bin/stat -c 'in execed process: %u' /proc/self/fd
before: owner of /proc/self/fd: 1000
after: owner of /proc/self/fd: 0
in execed process: 1000
The reason I want this is that I have a program which runs as a regular user and reads sensitive data (passwords) from stdin, and I don't want others to be able to read this process data from /proc. Is there some alternative way to do this?
I don't want others to be able to read this process data from /proc. Is there some alternative way to do this?
The contents of the per-process subdirectories in /proc are ordinarily owned by the effective uid and effective gid of the corresponding process. The only documented condition in which that's different is when the process's dumpable attribute has a value different from 1.
The reason I want this is that I have a program which runs as a regular user and reads sensitive data (passwords) from stdin
If the program in question runs as a regular user that you can choose, then the easiest thing to do is to choose a for-purpose user with a private primary group. For the purpose of /proc access, that should be about as good as setting the process non-dumpable or running it as root. Maybe better, if the process wants to be able to read from its own /proc entry. This is pretty natural, too. It does not, however, serve the primary purpose of turning off the dumpable flag -- i.e. to prevent the process from dumping core.
If the program itself is under your control, then you can simply modify it to issue an appropriate prctl() call. I suppose that this is not your case.
Otherwise, the program cannot be modified and must be runnable by arbitrary users. According to the prctl() documentation, there are four ways other than calling prctl() to cause a process's dumpable flag to be turned off:
The process's effective user or group ID is changed.
The process's filesystem user or group ID is changed (see
credentials(7)).
The process executes (execve(2)) a set-user-ID or set-
group-ID program, resulting in a change of either the
effective user ID or the effective group ID.
The process executes (execve(2)) a program that has file
capabilities (see capabilities(7)), but only if the
permitted capabilities gained exceed those already
permitted for the process.
These all describe situations in which the process is differently affected by access controls than ordinary processes started by the same user, which comes back around to my original suggestion of arranging for appropriate access control simply by controlling the user as which the program runs. After all, who cares if a random user runs the program and is able to retrieve secrets that they themselves entered? An issue arises only if other people can be tricked into divulging secrets to the program. But if you're considering this alternative then you've already rejected the easy option.
With the program ultimately being launched by a non-privileged user, changing its effective or filesystem credentials, even via a wrapper program, is not a viable alternative by itself. You're left, then, with making the program SUID or SGID, or assigning it capabilities that you can rely on differing from the user's normal ones. Note that the SUID / SGID target, if you go that way, does not need to be root; it just needs to be different from the user's own. This does, however, return again to having a designated identity for the program to run as.
The capabilites option requires your system to support capabilities, of course. If it doesn't then SUID / SGID is your only remaining option. Both of these are controlled by attributes attached to the program binary in the filesystem, so they do not require you to modify the program itself.
Adding to the excellent answer of John Bollinger, a poorly documented alternative is to change owner of the program that is about to be executed to root, and remove read permission on the executable:
sudo chown root:root /path/to/program
sudo chmod go-rw /path/to/program
Still, there will be no inheritance of the dumpable flag across execve, but the kernel will set the flag every time that program is executed.
man 5 core gives the following reasoning:
This is a security measure to ensure that an executable whose contents are not readable does not produce a possibly readable core dump containing an image of the executable

Cannot open /proc/self/oom_score_adj when I have the right capability

I'm trying to set the OOM killer score adjustment for a process, inspired by oom_adjust_setup in OpenSSH's port_linux.c. To do that, I open /proc/self/oom_score_adj, read the old value, and write a new value. Obviously, my process needs to be root or have the capability CAP_SYS_RESOURCE to do that.
I'm getting a result that I can't explain. When my process doesn't have the capability, I'm able to open that file and read and write values, though the value I write doesn't take effect (fair enough):
$ ./a.out
CAP_SYS_RESOURCE: not effective, not permitted, not inheritable
oom_score_adj value: 0
wrote 5 bytes
oom_score_adj value: 0
But when my process does have the capability, I can't even open the file: it fails with EACCES:
$ sudo setcap CAP_SYS_RESOURCE+eip a.out
$ ./a.out
CAP_SYS_RESOURCE: effective, permitted, not inheritable
failed to open /proc/self/oom_score_adj: Permission denied
Why does it do that? What am I missing?
Some further googling led me to this lkml post by Azat Khuzhin on 20 Oct 2013. Apparently CAP_SYS_RESOURCE lets you change oom_score_adj for any process but yourself. To change your own score adjustment, you need to combine it with CAP_DAC_OVERRIDE - that is, disable access controls for all files. (If I wanted that, I would have made this program setuid root.)
So my question is, how can I achieve this without CAP_DAC_OVERRIDE?
I'm running Ubuntu xenial 16.04.4, kernel version 4.13.0-45-generic. My problem is similar to but different from this question: that's about an error on write, when not having the capability.
My sample program:
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/capability.h>
void read_value(FILE *fp)
{
int value;
rewind(fp);
if (fscanf(fp, "%d", &value) != 1) {
fprintf(stderr, "read failed: %s\n", ferror(fp) ? strerror(errno) : "cannot parse");
}
else {
fprintf(stderr, "oom_score_adj value: %d\n", value);
}
}
void write_value(FILE *fp)
{
int result;
rewind(fp);
result = fprintf(fp, "-1000");
if (result < 0) {
fprintf(stderr, "write failed: %s\n", strerror(errno));
}
else {
fprintf(stderr, "wrote %d bytes\n", result);
}
}
int main()
{
FILE *fp;
struct __user_cap_header_struct h;
struct __user_cap_data_struct d;
h.version = _LINUX_CAPABILITY_VERSION_3;
h.pid = 0;
if (0 != capget(&h, &d)) {
fprintf(stderr, "capget failed: %s\n", strerror(errno));
}
else {
fprintf(stderr, "CAP_SYS_RESOURCE: %s, %s, %s\n",
d.effective & (1 << CAP_SYS_RESOURCE) ? "effective" : "not effective",
d.permitted & (1 << CAP_SYS_RESOURCE) ? "permitted" : "not permitted",
d.inheritable & (1 << CAP_SYS_RESOURCE) ? "inheritable" : "not inheritable");
}
fp = fopen("/proc/self/oom_score_adj", "r+");
if (!fp) {
fprintf(stderr, "failed to open /proc/self/oom_score_adj: %s\n", strerror(errno));
return 1;
}
else {
read_value(fp);
write_value(fp);
read_value(fp);
fclose(fp);
}
return 0;
}
This one was very interesting to crack, took me a while.
The first real hint was this answer to a different question: https://unix.stackexchange.com/questions/364568/how-to-read-the-proc-pid-fd-directory-of-a-process-which-has-a-linux-capabil - just wanted to give the credit.
The reason it does not work as is
The real reason you get "permission denied" there is files under /proc/self/ are owned by root if the process has any capabilities - it's not about CAP_SYS_RESOURCE or about oom_* files specifically. You can verify this by calling stat and using different capabilities. Quoting man 5 proc:
/proc/[pid]
There is a numerical subdirectory for each running process; the subdirectory is named by the process ID.
Each /proc/[pid] subdirectory contains the pseudo-files and directories described below. These files are normally owned by the effective user and effective group ID of the process. However, as a security measure, the ownership is made root:root if the process's "dumpable" attribute is set to a value other than 1. This attribute may change for the following reasons:
The attribute was explicitly set via the prctl(2) PR_SET_DUMPABLE operation.
The attribute was reset to the value in the file /proc/sys/fs/suid_dumpable (described below), for the reasons described in prctl(2).
Resetting the "dumpable" attribute to 1 reverts the ownership of the /proc/[pid]/* files to the process's real UID and real GID.
This already hints to the solution, but first let's dig a little deeper and see that man prctl:
PR_SET_DUMPABLE (since Linux 2.3.20)
Set the state of the "dumpable" flag, which determines whether core dumps are produced for the calling process upon delivery of a signal whose default behavior is to produce a core dump.
In kernels up to and including 2.6.12, arg2 must be either 0 (SUID_DUMP_DISABLE, process is not dumpable) or 1 (SUID_DUMP_USER, process is dumpable). Between kernels 2.6.13 and 2.6.17, the value 2 was also permitted, which caused any binary which normally would not be dumped to be dumped readable by root only; for security reasons, this feature has been removed. (See also the description of /proc/sys/fs/suid_dumpable in proc(5).)
Normally, this flag is set to 1. However, it is reset to the current value contained in the file /proc/sys/fs/suid_dumpable (which by default has the value 0), in the following circumstances:
The process's effective user or group ID is changed.
The process's filesystem user or group ID is changed (see credentials(7)).
The process executes (execve(2)) a set-user-ID or set-group-ID program, resulting in a change of either the effective user ID or the effective group ID.
The process executes (execve(2)) a program that has file capabilities (see capabilities(7)), but only if the permitted capabilities gained exceed those already permitted for the process.
Processes that are not dumpable can not be attached via ptrace(2) PTRACE_ATTACH; see ptrace(2) for further details.
If a process is not dumpable, the ownership of files in the process's /proc/[pid] directory is affected as described in proc(5).
Now it's clear: our process has a capability that the shell used to launch it did not have, thus the dumpable attribute was set to false, thus files under /proc/self/ are owned by root rather than the current user.
How to make it work
The fix is as simple as re-setting that dumpable attribute before trying to open the file. Stick the following or something similar before opening the file:
prctl(PR_SET_DUMPABLE, 1, 0, 0, 0);
Hope that helps ;)
This is not an answer (dvk already provided the answer to the stated question), but an extended comment describing the often overlooked, possibly very dangerous side effects, of reducing /proc/self/oom_score_adj.
In summary, using prctl(PR_SET_DUMPABLE, 1, 0, 0, 0) will allow a process with CAP_SYS_RESOURCE capability (conveyed via e.g. filesystem capabilities) to modify the oom_score_adj of any other process owned by the same user, including their own.
(By default, a process that has capabilities is not dumpable, so a core dump is not generated even when the process is killed by a signal whose disposition is to generate a core.)
The dangers I'd like to comment on, are how the oom_score_adj range is inherited, and what it means to change it for processes that create child processes. (Thanks to dvk for some corrections.)
The Linux kernel maintains an internal value, oom_score_adj_min, for each process. The user (or the process itself) can modify the oom_score_adj to any value between oom_score_adj_min and OOM_SCORE_ADJ_MAX. The higher the value, the more likely the process is to be killed.
When a process is created, it will inherit its oom_score_adj_min from its parent. The original parent of all processes, init, has an initial oom_score_adj_min of 0.
To reduce the oom_score_adj below oom_score_adj_min, a process that either has superuser privileges, or has the CAP_SYS_RESOURCE and is dumpable, writes the new score to /proc/PID/oom_score_adj. In this case, oom_score_adj_min is also set to the same value.
(You can verify this by examining fs/proc/base.c:__set_oom_adj() in the Linux kernel; see the assignments to task->signal->oom_score_adj_min.)
The problem is that the oom_score_adj_min value sticks, except when update by a process that has the CAP_SYS_RESOURCE capability. (Note: I originally thought that it could not be raised at all, but I was wrong.)
For example, if you have a high-value service daemon that has its oom_score_adj_min reduced, running without the CAP_SYS_RESOURCE capability, increasing the oom_score_adj before forking child processes will cause the child processes to inherit the new oom_score_adj, but the original oom_score_adj_min. This means such child processes can reduce their oom_score_adj to that of their parent service daemon, without any privileges or capabilities.
(Because there are just two thousand and one possible oom_score_adj values (-1000 to 1000, inclusive), and only a thousand of those reduce the chance of a process to be killed (the negative ones, zero being the default) compared to "default", a nefarious process only needs to do ten or eleven writes to /proc/self/oom_score_adj to make the OOM killer avoid it as much as possible, by using a binary search: first, it will try -500. If it succeeds, the oom_score_adj_min is between -1000 and -500. If it fails, the oom_score_adj_min is between -499 and 1000. By halving the range at each attempt, it can set oom_score_adj to the kernel-internal minimum for that process, oom_score_adj_min, in ten or eleven writes, depending on what the initial oom_score_adj value was.)
Of course, there are mitigations and strategies to avoid the inheritance problem.
For example, if you have an important process that the OOM killer should leave alone, that should not create child processes, you should run it using a dedicated user account that has the RLIMIT_NPROC set to a suitably small value.
If you have a service that creates new child processes, but you want the parent to be less likely to be OOM killed than other processes, and you do not want the children to inherit that, there are two approaches that work.
Your service can at startup fork a child process to create further child processes, before it lowers its oom_score_adj. This makes the child processes inherit their oom_score_adj_min (and oom_score_adj) from the process that started the service.
Your service can keep CAP_SYS_RESOURCE in the CAP_PERMITTED set, but add or remove it from the CAP_EFFECTIVE set as needed.
When the CAP_SYS_RESOURCE is in the CAP_EFFECTIVE set, adjusting oom_score_adj also sets the oom_score_adj_min to that same value.
When CAP_SYS_RESOURCE is not in the CAP_EFFECTIVE set, you cannot decrement oom_score_adj below the corresponding oom_score_adj_min. oom_score_adj_min is unchanged even when oom_score_adj is modified.
It does make sense to put work that can be canceled/killed in an OOM situation into child processes with higher oom_score_adj values. If an OOM situation does occur -- for example, on an embedded appliance --, the core service daemon has a much higher chance of surviving, even when the worker child processes are killed. Of course, the core daemon itself should not be allocating dynamic memory in response to client requests, as any bug in it may not just crash that daemon, but bring the entire system to a halt (in an OOM situation where basically everything but the original cause, the core daemon, gets killed).

C - How to check if a process is a system process?

I'm trying to detect a fork bomb, and through that process I am trying to caluclate the descendant count of each process. However, I only want to calculate the descendant count for non-system processes, as the fork bomb will be a non-system process. However, I'm unsure how do do that. This is what I have so far:
struct task_struct *pTask;
for_each_process(pTask)
{
struct task_struct *p;
*p = *pTask;
//trace back to every ancestor
for(p = current; p != &init_task; p->parent)
{
//increment the descendant count of p's parent
}
}
This loop goes up to the init task correct, since it's &init_task? Is there any way to instead to go to the first system process and stop? Because for example, the fork bomb will be the immediate child of a system process. Any help would be greatly appreciated, thank you!!
[EDIT]
And by system process, I mean like things like for example, bash. I should have explained more clearly, but at the basic level, I don't want to delete any process that runs from boot-up. Any processes orginating from user-space that is run after boot up is fair game, but other processes are not. And I will not be checking for anything like tomcat,httpd, because I know 100% that those processes will not be running.
A login bash shell is execed by another process (which process depends on whether it is a console login shell, ssh login shell, gnome-terminal shell, etc.) The process execing bash is execed by init or some other process launched by init, not by the kernel.
A user can easily create a bash script that forks itself, so if you exempt /bin/bash from your checking then fork bombs written in bash will not be detected. For example, the following script, put in a file called foo and executed in the current directory, will create a fork bomb.
#!/bin/bash
while [ /bin/true ]
do
./foo &
done
Take a look at ulimit in bash(1) or setrlimit(2) if you want to limit the number of processes a particular user can run.
Or, you could set a really high threshold for the descendant count that triggers killing a process. If the chain of parents back to init is several hundred deep, then probably something fishy is going on.
You can use the logind / ConsoleKit2 D-Bus APIs to determine the session of a process by its PID. "System" processes (IIRC) will have no session.
Obviously this requires that logind (part of Systemd) or ConsoleKit2 are running.
Without such a service that tracks user sessions, there may be no reliable way to differentiate a "system" process from a user process, except perhaps by user-id (assuming that your fork bomb won't be running as a system user). On many distributions system user IDs are less than 1000, and regular user IDs are >= 1000, except for the "nobody" user (normally 65534).

SIGHUP from a terminal

Experts,
I have a client that connects over ssh to a server (it gets a tty allocated). I have a process A that is running on the server. Now, whenever the client disconnects, I need A to know about the tty that vanishes.
I was thinking since SSHD knows the session dying (after timeout or a simple exit), it can generate a signal to process A.
Is there any other way that A can get information about the tty that vanishes like listening on SIGHUP for the tty? I am writing the code in C on Linux.
Appreciate your help.
POSIX.1 provides a facility, utmpx, which lists the currently logged in users, their terminals, and other information. In Linux, that is the same as utmp; see man 5 utmp for further information.
OpenSSH does maintain utmp records.
Here is a simple example, that lists all users currently logged in from remote machines, the terminal they are using, and the initial process group the user owns:
#define _POSIX_C_SOURCE 200809L
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <utmpx.h>
int main(void)
{
struct utmpx *entry;
setutxent();
while ((entry = getutxent()))
if (entry->ut_type == USER_PROCESS && entry->ut_host[0] != '\0')
printf("%s is logged in on /dev/%s from %s and owns process group %d\n",
entry->ut_user, entry->ut_line, entry->ut_host,
(int)getpgid(entry->ut_pid));
return 0;
}
In your case, I would expect process A to maintain a list of remotely connected users, and periodically do a similar loop as above to update the status of known entries and to add new entries; and remove entries that are no longer seen.
New entries then match a "login" event, entries that are no longer seen a "logout" event (and deleted after the loop), and all other events are "still logged in" users.
The loop above is quite lightweight in terms of CPU time used and I/O used. The utmp records (/var/run/utmp in most Linux machines) are in binary form, and if frequently accessed, usually in page cache. Entries are relatively small, and even on servers with a lot of users the file read is well under a megabyte in size. Still, I wouldn't do it in a tight loop.
Personally, I would use inotify to wait for CLOSE_WRITE events on the UTMPX_FILE file (/var/run/utmp on most Linux machines), and reread the records after each event. That way the service would block on the read() on the inotify file descriptor most of the time (not wasting any CPU time), and pretty much immediately react to any login/logout events.
You face two problems, both difficult. The succinct answer is "you can't"; the longer answer is "you can't without making significant modifications".
A signal relays very little information other than the fact that it occurred. If you use sigaction() and SA_SIGINFO, you can find the process ID of the process that sent the signal, but under your scheme, that would be sshd, which isn't dreadfully helpful. Thus, it will be hard (nigh on impossible) to get the information about which terminal via the signal. Obviously, other schemes can be defined, but you'd have to write the information to a file, or something similar.
You'd have to modify sshd to record the information about which terminal it allocates (or is allocated) to its child processes, and then arrange for it to send that information to your Process A when a child terminates. That would be tricky, at best.
These two factors alone make it rather difficult. If you still want to do it, then the way I'd try is by getting sshd to run a special process of your devising, which in turn forks and the child runs the the process that sshd would otherwise run. The parent (a) records which terminal the child is connected to, and (b) waits for the child to terminate. When it does, it writes the terminal information to somewhere that Process A will find it, and exits. You still have to revise sshd, and you have to devise a mechanism whereby the parent process knows what to run as the child process (but that's probably not very hard; you leave the argument list unchanged, but simply have sshd exec your monitor process instead of whatever is specified as argv[0]…the parent uses argv[0] as the file argument to execvp().
This scheme minimizes the changes to sshd (but does still require a non-standard version). And you have to write the parent code carefully, and it has to cooperate with Process A. All decidedly non-trivial.

setuid equivalent for non-root users

Does Linux have some C interface similar to setuid, which allows a program to switch to a different user using e.g. the username/password? The problem with setuid is that it can only be used by superusers.
I am running a simple web service which requires jobs to be executed as the logged in user. So the main process runs as root, and after the user logs in it forks and calls setuid to switch to the appropriate uid. However, I am not quite comfortable with the main proc running as root. I would rather have it run as another user, and have some mechanism to switch to another user similar to su (but without starting a new process).
First, setuid() can most definitely be used by non-superusers. Technically, all you need in Linux is the CAP_SETUID (and/or CAP_SETGID) capability to switch to any user. Second, setuid() and setgid() can change the process identity between the real (user who executed the process), effective (owner of the setuid/setgid binary), and saved identities.
However, none of that is really relevant to your situation.
There exists a relatively straightforward, yet extremely robust solution: Have a setuid root helper, forked and executed by your service daemon before it creates any threads, and use an Unix domain socket pair to communicate between the helper and the service, the service passing both its credentials and the pipe endpoint file descriptors to the helper when user binaries are to be executed. The helper will check everything securely, and if all is in order, it will fork and execute the desired user helper, with the specified pipe endpoints connected to standard input, standard output, and standard error.
The procedure for the service to start the helper, as early as possible, is as follows:
Create an Unix domain socket pair, used for privileged communications between the service and the helper.
Fork.
In the child, close all excess file descriptors, keeping only one end of the socket pair. Redirect standard input, output, and error to /dev/null.
In the parent, close the child end of the socket pair.
In the child, execute the privileged helper binary.
The parent sends a simple message, possibly one without any data at all, but with an ancillary message containing its credentials.
The helper program waits for the initial message from the service.
When it receives it, it checks the credentials. If the credentials do not pass muster, it quits immediately.
The credentials in the ancillary message define the originating process' UID, GID, and PID. Although the process needs to fill in these, the kernel verifies they are true. The helper of course verifies that UID and GID are as expected (correspond to the account the service ought to be running as), but the trick is to get the statistics on the file the /proc/PID/exe symlink points to. That is the genuine executable of the process that sent the credentials. You should verify it is the same as the installed system service daemon (owned by root:root, in the system binary directory).
There is a very simple attack that may defeat the security up to this point. A nefarious user may create their own program, that forks and executes the helper binary correctly, sends the initial message with its true credentials -- but replaces itself with the correct system binary before the helper has a chance to check what the credentials actually refer to!
That attack is trivially defeated by three further steps:
The helper program generates a (cryptographically secure) pseudorandom number, say 1024 bits, and sends it back to the parent.
The parent sends the number back, but again adds its credentials in an ancillary message.
The helper program verifies that the UID, GID, and PID have not changed, and that /proc/PID/exe still points to the correct service daemon binary. (I'd just repeat the full checks.)
At step 8, the helper has already ascertained the other end of the socket is executing the binary it ought to be executing. Sending it a random cookie it has to send back, means the other end cannot have "stuffed" the socket with the messages beforehand. Of course this assumes the attacker cannot guess the pseudorandom number beforehand. If you want to be careful, you can read a suitable cookie from /dev/random, but remember it is a limited resource (may block if there is not enough randomness available to the kernel). I'd personally just read say 1024 bits (128 bytes) from /dev/urandom, and use that.
At this point, the helper has ascertained the other end of the socket pair is your service daemon, and the helper can trust the control messages as far as it can trust the service daemon. (I'm assuming this is the only mechanism the service daemon will spawn user processes; otherwise you'd need to re-pass the credentials in every further message, and re-check them every time in the helper.)
Whenever the service daemon wishes to execute a user binary, it
Creates the necessary pipes (one for feeding standard input to the user binary, one to get back the standard output from the user binary)
Sends a message to the helper containing
Identity to run the binary as; either user (and group) names, or UID and GID(s)
Path to the binary
Command-line parameters given to the binary
An ancillary message containing the file descriptors for the user binary endpoints of the data pipes
Whenever the helper gets such a message, it forks. In the child, it replaces standard input and output with the file descriptors in the ancillary message, changes identity with setresgid() and setresuid() and/or initgroups(), changes the working directory to somewhere appropriate, and executes the user binary. The parent helper process closes the file descriptors in the ancillary message, and waits for the next message.
If the helper exits when there is going to be no more input from the socket, then it will automatically exit when the service exits.
I could provide some example code, if there is sufficient interest. There's lots of details to get right, so the code is a bit tedious to write. However, correctly written, it is more secure than e.g. Apache SuEXEC.
No, there is no way to change UID using only a username and password. (The concept of a "password" is not recognized by the kernel in any fashion -- it only exists in userspace.) To switch from one non-root UID to another, you must become root as an intermediate step, typically by exec()-uting a setuid binary.
Another option in your situation may be to have the main server run as an unprivileged user, and have it communicate with a back-end process running as root.

Resources