Question

My custom program executes with non-root privileges with user IDs as: uid: 1000 euid: 0 in which after fork(), in child process execv() is invoked to run SSH client service. Since I launch the program under unprivileged user, when trying to bind the socket to a device Linux kernel performs all the permission checks, causing a failure in subroutine sock_setbindtodevice() while checking for CAP_NET_RAW capability as shown below.

Solution I was thinking about is to first get the root privileges in the child process, perform privileged operation such as setting the required capabilities, and drop back to non-root.

A question here is, what is needed to drop down to non-root, since when the ssh command is executed, I want the generated DSA/RSA keys to be stored in $HOME/.ssh/known_hosts rather than root/.ssh/known_hosts.

Please find the code snippet below:

void
global_exec_func (const char *proc_name, const char *proc_path, char **arg_list)
{
    pid_t   pid;
    int     status, euid;

    struct __user_cap_header_struct cap_header_data;
    cap_user_header_t cap_header = &cap_header_data;
    struct __user_cap_data_struct cap_data_data;
    cap_user_data_t cap_data = &cap_data_data;

    pid = fork();
    if (pid < 0) {
       printf("%% can't fork process %s", proc_name);
       return;
    }   

    /*  
     * Child process.
     */
    if (pid == 0) {
         euid = geteuid();          /* Storing euid */

         /*Gaining root privileges */
         if (setuid(0) < 0) {       
             printf("setuid(0) failed");
         }
         printf("After setting: getuid: %d geteuid: %d\n", getuid(),
                  geteuid());

         cap_header->pid = 0;
         cap_header->version = _LINUX_CAPABILITY_VERSION;

         /* Get the capabilities */
         if(capget(cap_header, cap_data)  < 0) {
             printf("failed capget error:%s", strerror(errno));
         }
         cap_data->effective = (1 << CAP_NET_RAW);    /* Set bit 13 */
         cap_data->inheritable = 0;

         /* Set the capabilities */
         if (capset(cap_header, cap_data) < 0) {
             printf("failed capset error:%s", strerror(errno));
         }

         /* Drop back privileges */
         if (seteuid(euid) < 0) {    
             printf("seteuid(euid) failed");
         }
         printf("After drop: getuid: %d geteuid: %d\n", getuid(), geteuid());

         prctl(PR_SET_KEEPCAPS, 1);
         execv(proc_path, arg_list);
         exit(1);
     }
     /*
      * Parent Process code follows
      */


Result:  
 [local]linux#ssh 101.1.1.101
 After setting: getuid: 0 geteuid: 0
 After drop: getuid: 0 geteuid: 0
 The authenticity of host '101.1.1.101 (101.1.1.101)' can't be established.
 DSA key fingerprint is 0c:61:df:01:93:74:1f:5f:49:34:f4:4e:06:e8:d7:5f.
 Are you sure you want to continue connecting (yes/no)? ^C
 [local]linux#

Results show that ssh is successful but the program is running as a root at this point, which is incorrect. How can I get back to UIDs as uid: 1000 euid: 0 so that the ssh keys are stored in the right directory.

Kindly comment and suggest on my solution, does it really solve the problem?

Was it helpful?

Solution

If your program executes with effective user ID root, then you do have root privileges.


In Linux, capabilities are divided into three sets: inheritable, permitted, and effective. Inheritable defines which capabilities stay permitted across an exec(). Permitted defines which capabilities are permitted for a process. Effective defines which capabilities are currently in effect.

Edited to add: When the filesystem containing the binary that will be exec()'d does support filesystem capabilities, these always affect what capabilities the executed process will have. See the Transformation of capabilities during an execve() in the man 7 capabilities man page.

When changing the owner or group of a process from root to non-root, the effective capability set is always cleared. By default, also the permitted capability set is cleared, but calling prctl(PR_SET_KEEPCAPS, 1L) before the identity change tells the kernel to keep the permitted set intact.

Therefore, to have the CAP_NET_RAW capability, your program has to have it in both permitted and effective sets. If you wish for the CAP_NET_RAW to remain in effect over an exec(), it must be included in all three capability sets.

Edited to add: If file capabilities are supported for the target of the exec(), the file capabilities must also contain those capabilities in the inherited and effective sets. (Only including the capability in the inherited and effective sets does not grant the capability, since it's not in the permitted set in the file capabilities; but, it is enough to allow passing the capability from the executor to the execee, if the executor has the capability).


You can use the setcap command to grant specific capabilities to a binary. (Most Linux filesystems nowadays support these file capabilities.) It does not need to be privileged, or setuid. Just remember to add the desired capabilities to both permitted and effective sets.

Edited to add some examples:

Grant CAP_NET_RAW to /usr/bin/myprog (which must NOT be setuid or setgid root):

sudo setcap 'cap_net_raw=pe' /usr/bin/myprog

By default, do not grant CAP_NET_RAW to /usr/bin/myprog, but if the executor has the capability (in both inheritable and permitted sets), retain the capability (in inheritable and permitted sets, and activating it in the effective set):

sudo setcap 'cap_net_raw=ie' /usr/bin/myprog

If your program has to be setuid root anyway, then you can use e.g.

#define  _GNU_SOURCE
#include <unistd.h>
#include <sys/types.h>
#include <sys/capability.h>
#include <sys/prctl.h>

#define   NEED_CAPS 1
static const cap_value_t need_caps[NEED_CAPS] = { CAP_NET_RAW };

int main(void)
{
    uid_t  real = getuid();
    cap_t  caps;

    /* Elevate privileges */
    if (setresuid(0, 0, 0))
        return 1; /* Fatal error, probably not setuid root */

    /* Add need_caps to current capabilities. */
    caps = cap_get_proc();
    if (cap_set_flag(caps, CAP_PERMITTED,   NEED_CAPS, need_caps, CAP_SET) ||
        cap_set_flag(caps, CAP_EFFECTIVE,   NEED_CAPS, need_caps, CAP_SET) ||
        cap_set_flag(caps, CAP_INHERITABLE, NEED_CAPS, need_caps, CAP_SET))
        return 1; /* Fatal error */

    /* Update capabilities */
    if (cap_set_proc(caps))
        return 1; /* Fatal error */

    /* Retain capabilities over an identity change */
    if (prctl(PR_SET_KEEPCAPS, 1L))
        return 1; /* Fatal error */

    /* Return to original, real-user identity */ 
    if (setresuid(real, real, real))
        return 1; /* Fatal error */

    /* Because the identity changed, we need to
     * re-install the effective set. */
    if (cap_set_proc(caps))
        return 1; /* Fatal error */

    /* Capability set is no longer needed. */
    cap_free(caps);

    /* You now have the CAP_NET_RAW capability.
     * It will be retained over fork() and exec().
    */

    return 0;
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top