SOLVED: Linux Capabilities-laced binary for running docker stats command?

CJT

New Member
Joined
Oct 2, 2020
Messages
2
Reaction score
0
Credits
46
Hi,

I have a scenario where a normal user process needs to call docker stats command, which is an elevated (root) call. The idea is to write a simple helper binary that adds the appropriate caps to the inheritable and ambient sets (and then set the caps on the binary from a setup script that runs as root...). However, this approach does not work since you can't connect to the docker daemon as a normal user, for good reason... I even set CAP_SYS_ADMIN as a test (obviously using this cap for a helper binary is not part of the plan, for obvious reasons...) and it did not work. It fails with

Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get http://%2Fvar%2Frun%2Fdocker.sock/v1.40/version: dial unix /var/run/docker.sock: connect: permission denied


Is this possible to pull off with capabilities?

Here is the sample helper binary code:


#include <stdio.h>
#include <unistd.h>
#include <sys/capability.h>
#include <sys/prctl.h>
/*
P'(ambient) = (file is privileged) ? 0 : P(ambient)

P'(permitted) = (P(inheritable) & F(inheritable)) |
(F(permitted) & P(bounding)) | P'(ambient)

P'(effective) = F(effective) ? P'(permitted) : P'(ambient)
*/

static void set_ambient_caps(int *newcaps, int num_elem)
{
int i;
for(i=0; i<num_elem; i++)
{
if(prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, newcaps, 0, 0)){
printf("Fail!\n");
return;
}
printf("Success!\n");
}
}

int main()
{
cap_value_t newcaps[2] = {CAP_NET_ADMIN, CAP_SYS_ADMIN};

// Add capabilities in the Inheritable set.
cap_t caps = cap_get_proc();
printf("Capabilities: %s\n", cap_to_text(caps, NULL));
cap_set_flag(caps, CAP_INHERITABLE, 2, newcaps, CAP_SET);
cap_set_proc(caps);
printf("Capabilities: %s\n", cap_to_text(caps, NULL));
cap_free(caps);

// Set ambient capabilities.
set_ambient_caps(newcaps, sizeof(newcaps)/sizeof(newcaps[0]));

char *param[] = {"docker", "stats", "--no-stream", NULL};
execv("/usr/bin/docker", param);
}

The setup script is

sudo setcap CAP_NET_ADMIN,CAP_SYS_ADMIN+ep ./elevated_docker

Adding the normal user to docker group is a security hole since this would mean any process running as this user could do stuff like kill container instances, run commands in containers, etc... The helper binary can only do one thing: execv docker stats --no-stream. Besides adding caps to the appropriate sets, this is all it does, so an attacker running as the normal user in this case could not gain access to other docker facilities. Again, I do not want to use the group approach because that is just as bad as running the calling process as root (with respect to what it does with docker containers), which we do not want to do...

Thanks for any suggestions,
Charles
 


I figured it out -> CAP_DAC_READ_SEARCH,CAP_DAC_OVERRIDE...

static void set_ambient_caps(int *newcaps, int num_elem)
{
int i;
for(i=0; i<num_elem; i++)
{
if(prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, newcaps, 0, 0)){
printf("Fail!\n");
return;
}
printf("Success!\n");
}
}

int main()
{
cap_value_t newcaps[2] = {CAP_DAC_READ_SEARCH,CAP_DAC_OVERRIDE};

//Add capabilities in the Inheritable set.
cap_t caps = cap_get_proc();
printf("Capabilities: %s\n", cap_to_text(caps, NULL));
cap_set_flag(caps, CAP_INHERITABLE, 2, newcaps, CAP_SET);
cap_set_proc(caps);
printf("Capabilities: %s\n", cap_to_text(caps, NULL));
cap_free(caps);

// Set ambient capabilities.
set_ambient_caps(newcaps, sizeof(newcaps)/sizeof(newcaps[0]));

char *param[] = {"docker", "stats", "--no-stream", NULL};
execv("/usr/bin/docker", param);
}

... setup script

sudo setcap CAP_DAC_READ_SEARCH,CAP_DAC_OVERRIDE+p ./elevated_docker
 
Last edited:

Members online


Latest posts

Top