AF_VSOCK must not provide host communication through the ia32 socketcall compatibility path. Accept EPERM or EACCES when socket creation is denied. If creation succeeds, connect to a CID-local host listener and exchange distinct payloads in both directions. Fail as soon as the host listener accepts the connection, and retain an AF_INET socketcall success case to ensure that the restriction is targeted. The AF_VSOCK case is expected to fail on amd64 until policy or namespace isolation closes the communication path. Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
165 lines
4.1 KiB
C
165 lines
4.1 KiB
C
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <stdint.h>
|
|
#include <signal.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/mman.h>
|
|
|
|
#define SYS_SOCKETCALL_I386 102
|
|
#define SYS_SOCKET 1
|
|
#define SYS_CONNECT 3
|
|
|
|
#ifndef SOCK_FAMILY
|
|
#error "define SOCK_FAMILY via -DSOCK_FAMILY=..."
|
|
#endif
|
|
#ifndef SOCK_TYPE
|
|
#error "define SOCK_TYPE via -DSOCK_TYPE=..."
|
|
#endif
|
|
|
|
#ifdef VSOCK_CONNECT
|
|
#ifndef PORT
|
|
#error "define PORT via -DPORT=..."
|
|
#endif
|
|
#ifndef CLIENT_PAYLOAD
|
|
#error "define CLIENT_PAYLOAD via -DCLIENT_PAYLOAD=..."
|
|
#endif
|
|
#ifndef EXPECTED_SERVER_PAYLOAD
|
|
#error "define EXPECTED_SERVER_PAYLOAD via -DEXPECTED_SERVER_PAYLOAD=..."
|
|
#endif
|
|
#ifndef CONNECT_DENIED_EXIT_CODE
|
|
#error "define CONNECT_DENIED_EXIT_CODE via -DCONNECT_DENIED_EXIT_CODE=..."
|
|
#endif
|
|
|
|
static void timeout(int signal) {
|
|
(void)signal;
|
|
static const char message[] = "socketcall client timed out\n";
|
|
write(STDERR_FILENO, message, sizeof(message) - 1);
|
|
_exit(124);
|
|
}
|
|
|
|
static int write_full(int fd, const char *buf, size_t len) {
|
|
while (len > 0) {
|
|
ssize_t n = write(fd, buf, len);
|
|
if (n < 0 && errno == EINTR)
|
|
continue;
|
|
if (n <= 0) {
|
|
if (n == 0)
|
|
errno = EIO;
|
|
perror("write");
|
|
return -1;
|
|
}
|
|
buf += n;
|
|
len -= (size_t)n;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int read_full(int fd, char *buf, size_t len) {
|
|
while (len > 0) {
|
|
ssize_t n = read(fd, buf, len);
|
|
if (n < 0 && errno == EINTR)
|
|
continue;
|
|
if (n < 0) {
|
|
perror("read");
|
|
return -1;
|
|
}
|
|
if (n == 0) {
|
|
fprintf(stderr, "unexpected EOF while reading server payload\n");
|
|
return -1;
|
|
}
|
|
buf += n;
|
|
len -= (size_t)n;
|
|
}
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
int main() {
|
|
/*
|
|
* The int $0x80 ia32 compat path truncates all registers to 32 bits.
|
|
* Every pointer used by socketcall must live below 4 GB, so keep its
|
|
* arguments and address in the MAP_32BIT allocation.
|
|
*/
|
|
unsigned char *mapping = mmap(NULL, 4096,
|
|
PROT_READ | PROT_WRITE,
|
|
MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT,
|
|
-1, 0);
|
|
if (mapping == MAP_FAILED) {
|
|
perror("mmap");
|
|
return 2;
|
|
}
|
|
unsigned int *args = (unsigned int *)mapping;
|
|
args[0] = SOCK_FAMILY;
|
|
args[1] = SOCK_TYPE;
|
|
args[2] = 0;
|
|
|
|
int ret;
|
|
asm volatile (
|
|
"int $0x80"
|
|
: "=a"(ret)
|
|
: "a"(SYS_SOCKETCALL_I386), "b"(SYS_SOCKET), "c"(args)
|
|
: "memory"
|
|
);
|
|
|
|
if (ret < 0) {
|
|
errno = -ret;
|
|
perror("socket");
|
|
return 1;
|
|
}
|
|
|
|
#ifdef VSOCK_CONNECT
|
|
signal(SIGALRM, timeout);
|
|
alarm(10);
|
|
|
|
int fd = ret;
|
|
struct sockaddr_vm *address = (struct sockaddr_vm *)(mapping + 64);
|
|
memset(address, 0, sizeof(*address));
|
|
address->svm_family = AF_VSOCK;
|
|
address->svm_cid = VMADDR_CID_LOCAL;
|
|
address->svm_port = PORT;
|
|
|
|
args[0] = (unsigned int)fd;
|
|
args[1] = (unsigned int)(uintptr_t)address;
|
|
args[2] = sizeof(*address);
|
|
asm volatile (
|
|
"int $0x80"
|
|
: "=a"(ret)
|
|
: "a"(SYS_SOCKETCALL_I386), "b"(SYS_CONNECT), "c"(args)
|
|
: "memory"
|
|
);
|
|
if (ret < 0) {
|
|
int error = -ret;
|
|
errno = error;
|
|
perror("connect");
|
|
close(fd);
|
|
if (error == EPERM || error == EACCES)
|
|
return CONNECT_DENIED_EXIT_CODE;
|
|
return 1;
|
|
}
|
|
|
|
static const char client_payload[] = CLIENT_PAYLOAD;
|
|
static const char server_payload[] = EXPECTED_SERVER_PAYLOAD;
|
|
char reply[sizeof(server_payload) - 1];
|
|
if (write_full(fd, client_payload, sizeof(client_payload) - 1) < 0 ||
|
|
read_full(fd, reply, sizeof(reply)) < 0) {
|
|
close(fd);
|
|
return 1;
|
|
}
|
|
if (memcmp(reply, server_payload, sizeof(reply)) != 0) {
|
|
fprintf(stderr, "server payload mismatch\n");
|
|
close(fd);
|
|
return 1;
|
|
}
|
|
alarm(0);
|
|
printf("AF_VSOCK connect via socketcall succeeded\n");
|
|
close(fd);
|
|
return 0;
|
|
#else
|
|
printf("socket(%d, %d, 0) via socketcall succeeded\n", SOCK_FAMILY, SOCK_TYPE);
|
|
close(ret);
|
|
return 0;
|
|
#endif
|
|
}
|