/* * Copyright (C) 2014 Red Hat, Inc. * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "config.h" #include "cockpitframe.h" #include #include #include #include #include #include #include #define MAX_FRAME_SIZE_BYTES 8 /** * cockpit_frame_parse: * @input: An buffer of bytes * @length: The length of @input buffer * @consumed: Number of bytes consumed from @input * * Parse message framing length string from the top * of the @input buffer. These are used by Cockpit transport framing * over a stream based protocol. * * Returns: The length, zero if more data is needed, or -1 if an error. */ ssize_t cockpit_frame_parse (unsigned char *input, size_t length, size_t *consumed) { size_t size = 0; size_t i; assert (input != NULL || length == 0); size = 0; for (i = 0; i < length; i++) { /* Check invalid characters, prevent integer overflow, limit max length */ if (i >= MAX_FRAME_SIZE_BYTES || (char)(input[i]) < '0' || (char)(input[i]) > '9') break; size *= 10; size += (char)(input[i]) - '0'; } /* Want more data */ if (i == length) return 0; /* Improperly formatted if any of the following cases: * - no digits read * - digits not followed by newline * - size had a leading zero */ if (size == 0 || input[i] != '\n' || input[0] == '0') return -1; if (consumed) *consumed = i + 1; return size; } ssize_t cockpit_fd_write_all (int fd, unsigned char *data, size_t length) { ssize_t written = 0; ssize_t res; assert (data != NULL || length == 0); while (length > 0) { res = write (fd, data, length); if (res < 0) { if (errno != EAGAIN && errno != EINTR) return -1; } else { data += res; length -= res; written += res; } } return written; } ssize_t cockpit_frame_write (int fd, unsigned char *input, size_t length) { char *prefix = NULL; ssize_t ret = -1; int errn = 0; assert (length > 0); assert (input != NULL); if (asprintf (&prefix, "%u\n", (unsigned int)length) < 0) { errn = ENOMEM; goto out; } ret = cockpit_fd_write_all (fd, (unsigned char *)prefix, strlen (prefix)); if (ret > 0) ret = cockpit_fd_write_all (fd, input, length); if (ret < 0) errn = errno; out: free (prefix); if (ret < 0) errno = errn; return ret; } /* read_exactly: * @fd: a blocking file descriptor * @buffer: where to read to * @required_size: the exact number of bytes to read * @was_eof: if EOF was encountered * * Reads exactly @required_size bytes from @fd into @buffer. If the * correct number of bytes are read, then %TRUE is returned. * * On failure, returns %FALSE. If the failure was due to an underlying * read error, then this error will be stored into errno. If the * failure was due to an incorrect number of bytes being read, then * errno will be set to %EBADMSG. * * The only permitted exception is if @was_eof is non-%NULL. In that * case, if exactly 0 bytes are read from @fd (ie: EOF at the start of * the message) then @was_eof will be set to %TRUE and the call will * return successfully. Otherwise, @was_eof will be set to %FALSE on * successful completion. * * If the required number of bytes couldn't be read, then any number of * bytes may have been read from @fd. The only reasonable thing to do * at this point is to treat the connection as broken, and close @fd. * In particular, this is why @fd should never be non-blocking. */ static bool read_exactly (int fd, unsigned char *buffer, size_t required_size, bool *was_eof) { size_t offset = 0; while (offset < required_size) { ssize_t n = read (fd, buffer + offset, required_size - offset); if (n == -1) { if (errno == EINTR) continue; if (errno != ECONNRESET) return false; /* ECONNRESET is treated as EOF */ n = 0; } if (n == 0) { if (was_eof != NULL && offset == 0) { *was_eof = true; return true; } errno = EBADMSG; return false; } offset += n; } if (was_eof) *was_eof = false; return true; } ssize_t cockpit_frame_read (int fd, unsigned char **output) { /* We first need to read the size of the frame, followed by the * content of the frame. We want to do this efficiently as possible, * while avoiding to read() more than the frame (since we can't put * bytes back). MSG_PEEK is also not always available, since we're * often talking to a pipe. * * Fortunately, we have a reasonable approach for this. * * Empty frames are invalid (cockpit_frame_parse() rejects size == 0), * so the smallest possible frame has a length of at least 3: the * single-digit size, the newline, then the single byte of body. * Therefore it's always safe to read 3 bytes ("the initial read"). * * Conveniently, reading three bytes is also always enough to tell us * how many bytes it's safe to read in order to determine the size of * the frame: * * - if we read a digit or two, followed by a newline, then we * already know the size of the entire frame * * - if we read three digits, we know that the frame body is at * least 100 bytes long. Since the maximum size of the length * field is 8 (+1 for newline), we can safely read this entire * amount. * * - if we get something other than digits or newlines, it's an * error */ size_t n_read = 3; unsigned char headerbuf[MAX_FRAME_SIZE_BYTES + 1]; bool eof; if (!read_exactly (fd, headerbuf, n_read, &eof)) return -1; if (eof) { if (output) *output = NULL; return 0; } size_t n_consumed; ssize_t size = cockpit_frame_parse (headerbuf, n_read, &n_consumed); if (size == 0) { /* cockpit_frame_parse() asked to read more data. As explained * above, it's safe to read the rest of the buffer now (6 bytes). * This should always result in a defined (non-zero) result. */ if (!read_exactly (fd, headerbuf + n_read, sizeof headerbuf - n_read, NULL)) return -1; n_read = sizeof headerbuf; size = cockpit_frame_parse (headerbuf, n_read, &n_consumed); assert (size != 0); } if (size == -1) { errno = EBADMSG; return -1; } /* We now have size equal to the number of bytes we need to return. Add an extra byte for a NUL terminator, * to be friendly to callers for text frames. */ unsigned char *buffer = calloc (1, size + 1); if (buffer == NULL) return -1; /* ENOMEM */ /* Copy the non-consumed bytes from the header (might be zero) */ size_t bytes_from_header = n_read - n_consumed; memcpy (buffer, headerbuf + n_consumed, bytes_from_header); /* Get the rest of the body (might be zero) */ if (!read_exactly (fd, buffer + bytes_from_header, size - bytes_from_header, NULL)) { free (buffer); return -1; } if (output) *output = buffer; else free (buffer); return size; }