Merge pull request #1539 from brauner/2017-05-06/fix_abstract_unix_sockets

bugfixes: {segfaults, hashes, abstract unix sockets}
This commit is contained in:
Stéphane Graber 2017-05-08 18:08:22 -04:00 committed by GitHub
commit 218a8306c2
5 changed files with 70 additions and 34 deletions

View File

@ -55,8 +55,9 @@ int lxc_abstract_unix_open(const char *path, int type, int flags)
addr.sun_family = AF_UNIX; addr.sun_family = AF_UNIX;
len = strlen(&path[1]) + 1; len = strlen(&path[1]);
if (len >= sizeof(addr.sun_path) - 1) { /* do not enforce \0-termination */
if (len >= sizeof(addr.sun_path)) {
close(fd); close(fd);
errno = ENAMETOOLONG; errno = ENAMETOOLONG;
return -1; return -1;
@ -64,7 +65,7 @@ int lxc_abstract_unix_open(const char *path, int type, int flags)
/* addr.sun_path[0] has already been set to 0 by memset() */ /* addr.sun_path[0] has already been set to 0 by memset() */
strncpy(&addr.sun_path[1], &path[1], strlen(&path[1])); strncpy(&addr.sun_path[1], &path[1], strlen(&path[1]));
if (bind(fd, (struct sockaddr *)&addr, offsetof(struct sockaddr_un, sun_path) + len)) { if (bind(fd, (struct sockaddr *)&addr, offsetof(struct sockaddr_un, sun_path) + len + 1)) {
int tmp = errno; int tmp = errno;
close(fd); close(fd);
errno = tmp; errno = tmp;
@ -109,8 +110,9 @@ int lxc_abstract_unix_connect(const char *path)
addr.sun_family = AF_UNIX; addr.sun_family = AF_UNIX;
len = strlen(&path[1]) + 1; len = strlen(&path[1]);
if (len >= sizeof(addr.sun_path) - 1) { /* do not enforce \0-termination */
if (len >= sizeof(addr.sun_path)) {
close(fd); close(fd);
errno = ENAMETOOLONG; errno = ENAMETOOLONG;
return -1; return -1;
@ -118,7 +120,7 @@ int lxc_abstract_unix_connect(const char *path)
/* addr.sun_path[0] has already been set to 0 by memset() */ /* addr.sun_path[0] has already been set to 0 by memset() */
strncpy(&addr.sun_path[1], &path[1], strlen(&path[1])); strncpy(&addr.sun_path[1], &path[1], strlen(&path[1]));
if (connect(fd, (struct sockaddr *)&addr, offsetof(struct sockaddr_un, sun_path) + len)) { if (connect(fd, (struct sockaddr *)&addr, offsetof(struct sockaddr_un, sun_path) + len + 1)) {
int tmp = errno; int tmp = errno;
/* special case to connect to older containers */ /* special case to connect to older containers */
if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) == 0) if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) == 0)
@ -136,8 +138,8 @@ int lxc_abstract_unix_send_fd(int fd, int sendfd, void *data, size_t size)
struct msghdr msg = { 0 }; struct msghdr msg = { 0 };
struct iovec iov; struct iovec iov;
struct cmsghdr *cmsg; struct cmsghdr *cmsg;
char cmsgbuf[CMSG_SPACE(sizeof(int))]; char cmsgbuf[CMSG_SPACE(sizeof(int))] = {0};
char buf[1]; char buf[1] = {0};
int *val; int *val;
msg.msg_control = cmsgbuf; msg.msg_control = cmsgbuf;
@ -166,9 +168,9 @@ int lxc_abstract_unix_recv_fd(int fd, int *recvfd, void *data, size_t size)
struct msghdr msg = { 0 }; struct msghdr msg = { 0 };
struct iovec iov; struct iovec iov;
struct cmsghdr *cmsg; struct cmsghdr *cmsg;
char cmsgbuf[CMSG_SPACE(sizeof(int))];
char buf[1];
int ret, *val; int ret, *val;
char cmsgbuf[CMSG_SPACE(sizeof(int))] = {0};
char buf[1] = {0};
msg.msg_name = NULL; msg.msg_name = NULL;
msg.msg_namelen = 0; msg.msg_namelen = 0;
@ -210,8 +212,8 @@ int lxc_abstract_unix_send_credential(int fd, void *data, size_t size)
.uid = getuid(), .uid = getuid(),
.gid = getgid(), .gid = getgid(),
}; };
char cmsgbuf[CMSG_SPACE(sizeof(cred))]; char cmsgbuf[CMSG_SPACE(sizeof(cred))] = {0};
char buf[1]; char buf[1] = {0};
msg.msg_control = cmsgbuf; msg.msg_control = cmsgbuf;
msg.msg_controllen = sizeof(cmsgbuf); msg.msg_controllen = sizeof(cmsgbuf);
@ -239,9 +241,9 @@ int lxc_abstract_unix_rcv_credential(int fd, void *data, size_t size)
struct iovec iov; struct iovec iov;
struct cmsghdr *cmsg; struct cmsghdr *cmsg;
struct ucred cred; struct ucred cred;
char cmsgbuf[CMSG_SPACE(sizeof(cred))];
char buf[1];
int ret; int ret;
char cmsgbuf[CMSG_SPACE(sizeof(cred))] = {0};
char buf[1] = {0};
msg.msg_name = NULL; msg.msg_name = NULL;
msg.msg_namelen = 0; msg.msg_namelen = 0;

View File

@ -24,8 +24,10 @@
#ifndef __LXC_AF_UNIX_H #ifndef __LXC_AF_UNIX_H
#define __LXC_AF_UNIX_H #define __LXC_AF_UNIX_H
/* does not enforce \0-termination */
extern int lxc_abstract_unix_open(const char *path, int type, int flags); extern int lxc_abstract_unix_open(const char *path, int type, int flags);
extern int lxc_abstract_unix_close(int fd); extern int lxc_abstract_unix_close(int fd);
/* does not enforce \0-termination */
extern int lxc_abstract_unix_connect(const char *path); extern int lxc_abstract_unix_connect(const char *path);
extern int lxc_abstract_unix_send_fd(int fd, int sendfd, void *data, size_t size); extern int lxc_abstract_unix_send_fd(int fd, int sendfd, void *data, size_t size);
extern int lxc_abstract_unix_recv_fd(int fd, int *recvfd, void *data, size_t size); extern int lxc_abstract_unix_recv_fd(int fd, int *recvfd, void *data, size_t size);

View File

@ -74,14 +74,19 @@
lxc_log_define(lxc_commands, lxc); lxc_log_define(lxc_commands, lxc);
static int fill_sock_name(char *path, int len, const char *name, static int fill_sock_name(char *path, int len, const char *lxcname,
const char *lxcpath, const char *hashed_sock_name) const char *lxcpath, const char *hashed_sock_name)
{ {
const char *name;
char *tmppath; char *tmppath;
size_t tmplen; size_t tmplen;
uint64_t hash; uint64_t hash;
int ret; int ret;
name = lxcname;
if (!name)
name = "";
if (hashed_sock_name != NULL) { if (hashed_sock_name != NULL) {
ret = snprintf(path, len, "lxc/%s/command", hashed_sock_name); ret = snprintf(path, len, "lxc/%s/command", hashed_sock_name);
if (ret < 0 || ret >= len) { if (ret < 0 || ret >= len) {
@ -193,8 +198,11 @@ static int lxc_cmd_rsp_recv(int sock, struct lxc_cmd_rr *cmd)
rsp->data = rspdata; rsp->data = rspdata;
} }
if (rsp->datalen == 0) if (rsp->datalen == 0) {
DEBUG("command %s response data length is 0",
lxc_cmd_str(cmd->req.cmd));
return ret; return ret;
}
if (rsp->datalen > LXC_CMD_DATA_MAX) { if (rsp->datalen > LXC_CMD_DATA_MAX) {
ERROR("Command %s response data %d too long.", ERROR("Command %s response data %d too long.",
lxc_cmd_str(cmd->req.cmd), rsp->datalen); lxc_cmd_str(cmd->req.cmd), rsp->datalen);
@ -274,7 +282,7 @@ static int lxc_cmd(const char *name, struct lxc_cmd_rr *cmd, int *stopped,
int sock, ret = -1; int sock, ret = -1;
char path[sizeof(((struct sockaddr_un *)0)->sun_path)] = { 0 }; char path[sizeof(((struct sockaddr_un *)0)->sun_path)] = { 0 };
char *offset = &path[1]; char *offset = &path[1];
int len; size_t len;
int stay_connected = cmd->req.cmd == LXC_CMD_CONSOLE; int stay_connected = cmd->req.cmd == LXC_CMD_CONSOLE;
*stopped = 0; *stopped = 0;
@ -982,7 +990,7 @@ int lxc_cmd_init(const char *name, struct lxc_handler *handler,
* Although null termination isn't required by the API, we do it anyway * Although null termination isn't required by the API, we do it anyway
* because we print the sockname out sometimes. * because we print the sockname out sometimes.
*/ */
len = sizeof(path)-2; len = sizeof(path) - 2;
if (fill_sock_name(offset, len, name, lxcpath, NULL)) if (fill_sock_name(offset, len, name, lxcpath, NULL))
return -1; return -1;

View File

@ -4370,7 +4370,10 @@ int list_active_containers(const char *lxcpath, char ***nret,
*p2 = '\0'; *p2 = '\0';
if (is_hashed) { if (is_hashed) {
if (strncmp(lxcpath, lxc_cmd_get_lxcpath(p), lxcpath_len) != 0) char *recvpath = lxc_cmd_get_lxcpath(p);
if (!recvpath)
continue;
if (strncmp(lxcpath, recvpath, lxcpath_len) != 0)
continue; continue;
p = lxc_cmd_get_name(p); p = lxc_cmd_get_name(p);
} }

View File

@ -153,36 +153,52 @@ int lxc_monitor_close(int fd)
return close(fd); return close(fd);
} }
/* Enforces \0-termination for the abstract unix socket. This is not required
* but allows us to print it out.
*
* Older version of liblxc only allowed for 105 bytes to be used for the
* abstract unix domain socket name because the code for our abstract unix
* socket handling performed invalid checks. Since we \0-terminate we could now
* have a maximum of 106 chars. But to not break backwards compatibility we keep
* the limit at 105.
*/
int lxc_monitor_sock_name(const char *lxcpath, struct sockaddr_un *addr) { int lxc_monitor_sock_name(const char *lxcpath, struct sockaddr_un *addr) {
size_t len; size_t len;
int ret; int ret;
char *sockname;
char *path; char *path;
uint64_t hash; uint64_t hash;
/* addr.sun_path is only 108 bytes, so we hash the full name and /* addr.sun_path is only 108 bytes, so we hash the full name and
* then append as much of the name as we can fit. * then append as much of the name as we can fit.
*/ */
sockname = &addr->sun_path[1];
memset(addr, 0, sizeof(*addr)); memset(addr, 0, sizeof(*addr));
addr->sun_family = AF_UNIX; addr->sun_family = AF_UNIX;
/* strlen("lxc/") + strlen("/monitor-sock") + 1 = 18 */
len = strlen(lxcpath) + 18; len = strlen(lxcpath) + 18;
path = alloca(len); path = alloca(len);
ret = snprintf(path, len, "lxc/%s/monitor-sock", lxcpath); ret = snprintf(path, len, "lxc/%s/monitor-sock", lxcpath);
if (ret < 0 || (size_t)ret >= len) { if (ret < 0 || (size_t)ret >= len) {
ERROR("Failed to create path for monitor."); ERROR("failed to create name for monitor socket");
return -1; return -1;
} }
/* Note: snprintf() will \0-terminate addr->sun_path on the 106th byte
* and so the abstract socket name has 105 "meaningful" characters. This
* is absolutely intentional. For further info read the comment for this
* function above!
*/
len = sizeof(addr->sun_path) - 1; len = sizeof(addr->sun_path) - 1;
hash = fnv_64a_buf(path, ret, FNV1A_64_INIT); hash = fnv_64a_buf(path, ret, FNV1A_64_INIT);
ret = snprintf(sockname, len, "lxc/%016" PRIx64 "/%s", hash, lxcpath); ret = snprintf(addr->sun_path, len, "@lxc/%016" PRIx64 "/%s", hash, lxcpath);
if (ret < 0) if (ret < 0) {
ERROR("failed to create hashed name for monitor socket");
return -1; return -1;
}
sockname[sizeof(addr->sun_path)-3] = '\0'; /* replace @ with \0 */
INFO("Using monitor socket name \"%s\".", sockname); addr->sun_path[0] = '\0';
INFO("using monitor socket name \"%s\" (length of socket name %zu must be <= %zu)", &addr->sun_path[1], strlen(&addr->sun_path[1]), sizeof(addr->sun_path) - 3);
return 0; return 0;
} }
@ -193,7 +209,8 @@ int lxc_monitor_open(const char *lxcpath)
int fd; int fd;
size_t retry; size_t retry;
size_t len; size_t len;
int ret = 0, backoff_ms[] = {10, 50, 100}; int ret = -1;
int backoff_ms[] = {10, 50, 100};
if (lxc_monitor_sock_name(lxcpath, &addr) < 0) if (lxc_monitor_sock_name(lxcpath, &addr) < 0)
return -1; return -1;
@ -201,28 +218,32 @@ int lxc_monitor_open(const char *lxcpath)
fd = socket(PF_UNIX, SOCK_STREAM, 0); fd = socket(PF_UNIX, SOCK_STREAM, 0);
if (fd < 0) { if (fd < 0) {
ERROR("Failed to create socket: %s.", strerror(errno)); ERROR("Failed to create socket: %s.", strerror(errno));
return -1; return -errno;
} }
len = strlen(&addr.sun_path[1]) + 1; len = strlen(&addr.sun_path[1]);
DEBUG("opening monitor socket %s with len %zu", &addr.sun_path[1], len);
if (len >= sizeof(addr.sun_path) - 1) { if (len >= sizeof(addr.sun_path) - 1) {
ret = -1;
errno = ENAMETOOLONG; errno = ENAMETOOLONG;
ret = -errno;
ERROR("name of monitor socket too long (%zu bytes): %s", len, strerror(errno));
goto on_error; goto on_error;
} }
for (retry = 0; retry < sizeof(backoff_ms) / sizeof(backoff_ms[0]); retry++) { for (retry = 0; retry < sizeof(backoff_ms) / sizeof(backoff_ms[0]); retry++) {
ret = connect(fd, (struct sockaddr *)&addr, offsetof(struct sockaddr_un, sun_path) + len); fd = lxc_abstract_unix_connect(addr.sun_path);
if (ret == 0 || errno != ECONNREFUSED) if (fd < 0 || errno != ECONNREFUSED)
break; break;
ERROR("Failed to connect to monitor socket. Retrying in %d ms.", backoff_ms[retry]); ERROR("Failed to connect to monitor socket. Retrying in %d ms: %s", backoff_ms[retry], strerror(errno));
usleep(backoff_ms[retry] * 1000); usleep(backoff_ms[retry] * 1000);
} }
if (ret < 0) { if (fd < 0) {
ret = -errno;
ERROR("Failed to connect to monitor socket: %s.", strerror(errno)); ERROR("Failed to connect to monitor socket: %s.", strerror(errno));
goto on_error; goto on_error;
} }
ret = 0;
return fd; return fd;