From 1fb8bb790f5bd7d81a97be7507d1063aa3bb2c58 Mon Sep 17 00:00:00 2001 From: Amarnath Valluri Date: Wed, 20 Sep 2017 16:09:46 +0300 Subject: [PATCH] Support added to receive data socket over control socket As objected by QEMU upstream developers to use two different sockets for starting/using of swtpm, This commit adds support for passing unix domain socket over control channel. The summary of the changes include: - Defined new control command CMD_SET_DATAFD, using this clients can send data socket. - set mlp.fd and mlp.flags outside of the mainloop - updated the testcases Signed-off-by: Amarnath Valluri --- include/swtpm/tpm_ioctl.h | 3 ++ src/swtpm/ctrlchannel.c | 53 +++++++++++++++++++++--- src/swtpm/ctrlchannel.h | 3 +- src/swtpm/mainloop.c | 7 ++-- src/swtpm/swtpm_io.c | 7 ++-- tests/test_clientfds.py | 2 +- tests/test_ctrlchannel | 27 +++++++++++- tests/test_setdatafd.py | 87 +++++++++++++++++++++++++++++++++++++++ 8 files changed, 172 insertions(+), 17 deletions(-) create mode 100755 tests/test_setdatafd.py diff --git a/include/swtpm/tpm_ioctl.h b/include/swtpm/tpm_ioctl.h index 5a4a3fb..7c03a37 100644 --- a/include/swtpm/tpm_ioctl.h +++ b/include/swtpm/tpm_ioctl.h @@ -193,6 +193,7 @@ typedef struct ptm_getconfig ptm_getconfig; #define PTM_CAP_SET_STATEBLOB (1<<9) #define PTM_CAP_STOP (1<<10) #define PTM_CAP_GET_CONFIG (1<<11) +#define PTM_CAP_SET_DATAFD (1<<12) enum { PTM_GET_CAPABILITY = _IOR('P', 0, ptm_cap), @@ -210,6 +211,7 @@ enum { PTM_SET_STATEBLOB = _IOWR('P', 12, ptm_setstate), PTM_STOP = _IOR('P', 13, ptm_res), PTM_GET_CONFIG = _IOR('P', 14, ptm_getconfig), + PTM_SET_DATAFD = _IOR('P', 15, ptm_res), }; /* @@ -238,6 +240,7 @@ enum { CMD_SET_STATEBLOB, CMD_STOP, CMD_GET_CONFIG, + CMD_SET_DATAFD }; #endif /* _TPM_IOCTL_H */ diff --git a/src/swtpm/ctrlchannel.c b/src/swtpm/ctrlchannel.c index 76826f9..1ad9d01 100644 --- a/src/swtpm/ctrlchannel.c +++ b/src/swtpm/ctrlchannel.c @@ -58,6 +58,7 @@ #include "tpmlib.h" #include "swtpm_nvfile.h" #include "locality.h" +#include "mainloop.h" /* local variables */ @@ -389,7 +390,8 @@ wait_chunk: * number when set via CMD_SET_LOCALITY * @tpm_running: indicates whether the TPM is running; may be changed by * this function in case TPM is stopped or started - * @locality_flags: flags indicate how to handle locality 4 + * @mlp: mainloop parameters used; may be altered by this function incase of + * CMD_SET_DATAFD * * This function returns the passed file descriptor or -1 in case the * file descriptor was closed. @@ -399,7 +401,7 @@ int ctrlchannel_process_fd(int fd, bool *terminate, TPM_MODIFIER_INDICATOR *locality, bool *tpm_running, - uint32_t locality_flags) + struct mainLoopParams *mlp) { struct input input; struct output { @@ -409,12 +411,18 @@ int ctrlchannel_process_fd(int fd, struct iovec iov = { .iov_base = &input, .iov_len = sizeof(input) }; + char control[CMSG_SPACE(sizeof(int))]; struct msghdr msg = { .msg_iov = &iov, .msg_iovlen = 1, - .msg_control = NULL, - .msg_controllen = 0, + .msg_control = control, + .msg_controllen = sizeof(control), }; + struct cmsghdr *cmsg = NULL; + int sock_type = 0; + socklen_t len = 0; + int *data_fd = NULL; + /* Write-only */ ptm_cap *ptm_caps = (ptm_cap *)&output.body; ptm_res *res_p = (ptm_res *)&output.body; @@ -459,7 +467,8 @@ int ctrlchannel_process_fd(int fd, PTM_CAP_GET_STATEBLOB | PTM_CAP_SET_STATEBLOB | PTM_CAP_STOP | - PTM_CAP_GET_CONFIG); + PTM_CAP_GET_CONFIG | + PTM_CAP_SET_DATAFD); out_len = sizeof(*ptm_caps); break; @@ -551,7 +560,7 @@ int ctrlchannel_process_fd(int fd, pl = (ptm_loc *)input.body; if (pl->u.req.loc > 4 || (pl->u.req.loc == 4 && - locality_flags & LOCALITY_FLAG_REJECT_LOCALITY_4)) { + mlp->locality_flags & LOCALITY_FLAG_REJECT_LOCALITY_4)) { res = TPM_BAD_LOCALITY; } else { res = TPM_SUCCESS; @@ -683,6 +692,32 @@ int ctrlchannel_process_fd(int fd, out_len = sizeof(pgc->u.resp); break; + case CMD_SET_DATAFD: + if (mlp->fd != -1) + goto err_io; + + cmsg = CMSG_FIRSTHDR(&msg); + if (!cmsg || cmsg->cmsg_len < CMSG_LEN(sizeof(int)) || + cmsg->cmsg_level != SOL_SOCKET || + cmsg->cmsg_type != SCM_RIGHTS || + !(data_fd = (int *)CMSG_DATA(cmsg)) || + *data_fd < 0) { + logprintf(STDERR_FILENO, "no valid data socket in message; cmsg = " + "%p", cmsg); + goto err_bad_input; + } + + mlp->flags = MAIN_LOOP_FLAG_USE_FD | MAIN_LOOP_FLAG_KEEP_CONNECTION | + MAIN_LOOP_FLAG_END_ON_HUP; + if (!getsockopt(*data_fd, SOL_SOCKET, SO_TYPE, &sock_type, &len) + && sock_type != SOCK_STREAM) + mlp->flags |= MAIN_LOOP_FLAG_READALL; + mlp->fd = *data_fd; + + *res_p = htobe32(TPM_SUCCESS); + out_len = sizeof(ptm_res); + break; + default: logprintf(STDERR_FILENO, "Error: Unknown command: 0x%08x\n", be32toh(input.cmd)); @@ -720,6 +755,12 @@ err_not_running: goto send_resp; +err_io: + *res_p = htobe32(TPM_IOERROR); + out_len = sizeof(ptm_res); + + goto send_resp; + err_socket: close(fd); diff --git a/src/swtpm/ctrlchannel.h b/src/swtpm/ctrlchannel.h index 3a6c4a2..91e3192 100644 --- a/src/swtpm/ctrlchannel.h +++ b/src/swtpm/ctrlchannel.h @@ -42,6 +42,7 @@ struct ctrlchannel; struct libtpms_callbacks; +struct mainLoopParams; struct ctrlchannel *ctrlchannel_new(int fd, bool isclient); int ctrlchannel_get_fd(struct ctrlchannel *cc); @@ -51,6 +52,6 @@ int ctrlchannel_process_fd(int fd, bool *terminate, TPM_MODIFIER_INDICATOR *locality, bool *tpm_running, - uint32_t locality_flags); + struct mainLoopParams *mlp); #endif /* _SWTPM_CTRLCHANNEL_H_ */ diff --git a/src/swtpm/mainloop.c b/src/swtpm/mainloop.c index d17629f..75b4959 100644 --- a/src/swtpm/mainloop.c +++ b/src/swtpm/mainloop.c @@ -121,8 +121,6 @@ int mainLoop(struct mainLoopParams *mlp, sockfd = SWTPM_IO_GetSocketFD(); - readall = (mlp->flags & MAIN_LOOP_FLAG_READALL); - while (!mainloop_terminate) { while (rc == 0) { @@ -165,6 +163,8 @@ int mainLoop(struct mainLoopParams *mlp, } if (pollfds[DATA_CLIENT_FD].revents & POLLHUP) { + logprintf(STDERR_FILENO, "Data client disconnected\n"); + mlp->fd = -1; /* chardev and unixio get this signal, not tcp */ if (mlp->flags & MAIN_LOOP_FLAG_END_ON_HUP) { /* only the chardev terminates here */ @@ -183,7 +183,7 @@ int mainLoop(struct mainLoopParams *mlp, ctrlclntfd = ctrlchannel_process_fd(ctrlclntfd, callbacks, &mainloop_terminate, &locality, &tpm_running, - mlp->locality_flags); + mlp); if (mainloop_terminate) break; } @@ -199,6 +199,7 @@ int mainLoop(struct mainLoopParams *mlp, /* Read the command. The number of bytes is determined by 'paramSize' in the stream */ if (rc == 0) { + readall = (mlp->flags & MAIN_LOOP_FLAG_READALL); rc = SWTPM_IO_Read(&connection_fd, command, &command_length, max_command_length, mlp, readall); if (rc != 0) { diff --git a/src/swtpm/swtpm_io.c b/src/swtpm/swtpm_io.c index 3466de1..0dba352 100644 --- a/src/swtpm/swtpm_io.c +++ b/src/swtpm/swtpm_io.c @@ -205,10 +205,9 @@ TPM_RESULT SWTPM_IO_Init(void) if (rc == 0) { port_str = getenv("TPM_PORT"); if (port_str == NULL) { - logprintf(STDERR_FILENO, - "SWTPM_IO_Init: Error, TPM_PORT environment variable not " - "set\n"); - rc = TPM_IOERROR; + TPM_DEBUG("SWTPM_IO_Init: TPM_PORT environment variable not set. " + "Data channel file descriptor must be passed in.\n"); + return 0; } } diff --git a/tests/test_clientfds.py b/tests/test_clientfds.py index 2d16063..d85677a 100755 --- a/tests/test_clientfds.py +++ b/tests/test_clientfds.py @@ -59,7 +59,7 @@ def test_get_caps(): # test get capabilities # CMD_GET_CAPABILITY = 0x00 00 00 01 cmd_get_caps = bytearray([0x00,0x00,0x00,0x01]) - expected_caps = bytearray([0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0xff]) + expected_caps = bytearray([0x00,0x00,0x00,0x00,0x00,0x00,0x1f,0xff]) def toString(arr): return ' '.join('{:02x}'.format(x) for x in arr) diff --git a/tests/test_ctrlchannel b/tests/test_ctrlchannel index 9ded13c..fb303c9 100755 --- a/tests/test_ctrlchannel +++ b/tests/test_ctrlchannel @@ -56,7 +56,7 @@ PID="$(cat $PID_FILE)" # Get the capability bits: CMD_GET_CAPABILITY = 0x00 00 00 01 res="$(unix_tx '\x00\x00\x00\x01')" -exp=" 00 00 00 00 00 00 0f ff" +exp=" 00 00 00 00 00 00 1f ff" if [ "$res" != "$exp" ]; then echo "Error: Unexpected response from CMD_GET_CAPABILITY:" echo " actual : $res" @@ -172,7 +172,7 @@ exec 100<>/dev/tcp/localhost/65430 # Get the capability bits: CMD_GET_CAPABILITY = 0x00 00 00 01 res="$(unix_tx '\x00\x00\x00\x01')" -exp=" 00 00 00 00 00 00 0f ff" +exp=" 00 00 00 00 00 00 1f ff" if [ "$res" != "$exp" ]; then echo "Error: Socket TPM: Unexpected response from CMD_GET_CAPABILITY:" echo " actual : $res" @@ -611,4 +611,27 @@ rm -f $LOG_FILE echo "OK" +# Test CMD_SET_DATAFD +cp ${PWD}/${DIR}/data/tpmstate1/* ${TPMDIR} +$SWTPM_EXE socket --flags not-need-init \ + --ctrl type=unixio,path=$SOCK_PATH \ + --tpmstate dir=$TPMDIR -t --pid file=$PID_FILE \ + --log file=$LOG_FILE,level=20 & +PID=$! + +if wait_for_file $PID_FILE 3; then + echo "Error: Socket TPM did not write pidfile." + exit 1 +fi + +LOG=$(SOCK_PATH=$SOCK_PATH exec python $DIR/test_setdatafd.py) +RES=$? + +if [ $RES -ne 0 ]; then + echo "Error: CMD_SET_DATAFD failed: $LOG" + exit 1 +fi + +echo "OK" + exit 0 diff --git a/tests/test_setdatafd.py b/tests/test_setdatafd.py new file mode 100755 index 0000000..bf3e07f --- /dev/null +++ b/tests/test_setdatafd.py @@ -0,0 +1,87 @@ +#!/usr/bin/python + +import os, sys +import socket +import subprocess +import time +import struct +from array import array +if sys.version_info[0] < 3: + import _multiprocessing + +def toString(arr): + return ' '.join('{:02x}'.format(x) for x in arr) + +def test_ReadPCR10(fd): + send_data = bytearray(b"\x00\xC1\x00\x00\x00\x0C\x00\x00\x00\x99\x00\x01") + exp_data = bytearray([0x00, 0xC4, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x26]) + + try: + print("Sending data over ...."); + n = fd.send(send_data) + print("Written %d bytes " % n) + except socket.error as e: + print("SocketError") + fd.close() + return False + + buf = fd.recv(1024) + fd.close() + if buf: + if bytearray(buf) == exp_data: + return True + else: + print("Unexpected reply:\n actual: %s\n expected: %s" + % (toString(buf), toString(exp_data))) + return False + else: + print("Null reply from swtpm") + return False + +def test_SetDatafd(): + fd, _fd = socket.socketpair(socket.AF_UNIX, socket.SOCK_DGRAM) + sock_path = os.getenv('SOCK_PATH') + cmd_set_data_fd = bytearray([0x00,0x00,0x00,0x10]) + expected_res = bytearray([0x00,0x00,0x00,0x00]) + try: + fds = array("i") + fds.append(_fd.fileno()) + ctrlfd = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + print("Connecting to server at : %s" % sock_path) + ctrlfd.connect(sock_path) + print("Sending data fd over ctrl fd...") + if sys.version_info[0] < 3: + ctrlfd.send(cmd_set_data_fd) + _multiprocessing.sendfd(ctrlfd.fileno(), _fd.fileno()) + else: + ctrlfd.sendmsg([cmd_set_data_fd], + [(socket.SOL_SOCKET, socket.SCM_RIGHTS, fds)]) + except socket.error as e: + print("SocketError: " + str(e)) + ctrlfd.close() + + buf = ctrlfd.recv(4) + print("Received bytes.. : %s" % buf) + if buf: + caps = bytearray(buf) + if caps == expected_res: + return test_ReadPCR10(fd) + else: + print("Unexpected reply for CMD_SET_DATA_FD: \n actual: %s\n expected: %s" + % (toString(caps), toString(expected_res))) + return False + else: + print("Null reply from swtpm") + return False + +if __name__ == "__main__": + try: + if test_SetDatafd() == False: + res = 1 + else: + res = 0 + except: + print("__Exception: ", sys.exc_info()) + res = -1 + + sys.exit(res) -- 2.39.5