mirror of
http://git.haproxy.org/git/haproxy.git/
synced 2025-02-22 05:36:56 +00:00
usermsgs_buf.size is set without first checking if previous malloc attempt succeeded. This could fool the buffer API into assuming that the buffer is initialized, resulting in unsafe read/writes. Guarding usermsgs_buf.size assignment with the malloc attempt result to make the buffer initialization safe against malloc failures. This partially fixes GH #2130. It should be backported up to 2.6.
571 lines
13 KiB
C
571 lines
13 KiB
C
#include <sys/mman.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <syslog.h>
|
|
|
|
#include <haproxy/api.h>
|
|
#include <haproxy/applet-t.h>
|
|
#include <haproxy/buf.h>
|
|
#include <haproxy/cli.h>
|
|
#include <haproxy/errors.h>
|
|
#include <haproxy/global.h>
|
|
#include <haproxy/obj_type.h>
|
|
#include <haproxy/ring.h>
|
|
#include <haproxy/tools.h>
|
|
#include <haproxy/version.h>
|
|
|
|
/* A global buffer used to store all startup alerts/warnings. It will then be
|
|
* retrieve on the CLI. */
|
|
struct ring *startup_logs = NULL;
|
|
uint tot_warnings = 0;
|
|
#ifdef USE_SHM_OPEN
|
|
static struct ring *shm_startup_logs = NULL;
|
|
#endif
|
|
|
|
/* A thread local buffer used to store all alerts/warnings. It can be used to
|
|
* retrieve them for CLI commands after startup.
|
|
*/
|
|
#define USER_MESSAGES_BUFSIZE 1024
|
|
static THREAD_LOCAL struct buffer usermsgs_buf = BUF_NULL;
|
|
|
|
/* A thread local context used for stderr output via ha_alert/warning/notice/diag.
|
|
*/
|
|
#define USERMSGS_CTX_BUFSIZE PATH_MAX
|
|
static THREAD_LOCAL struct usermsgs_ctx usermsgs_ctx = { .str = BUF_NULL, };
|
|
|
|
#ifdef USE_SHM_OPEN
|
|
|
|
/* initialise an SHM for the startup logs and return its fd */
|
|
static int startup_logs_new_shm()
|
|
{
|
|
char *path = NULL;
|
|
int fd = -1;
|
|
int flags;
|
|
|
|
/* create a unique path per PID so we don't collide with another
|
|
process */
|
|
memprintf(&path, "/haproxy_startup_logs_%d", getpid());
|
|
fd = shm_open(path, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
|
|
if (fd == -1)
|
|
goto error;
|
|
shm_unlink(path);
|
|
ha_free(&path);
|
|
|
|
if (ftruncate(fd, STARTUP_LOG_SIZE) == -1)
|
|
goto error;
|
|
|
|
flags = fcntl(fd, F_GETFD);
|
|
if (flags == -1)
|
|
goto error;
|
|
flags &= ~FD_CLOEXEC;
|
|
flags = fcntl(fd, F_SETFD, flags);
|
|
if (flags == -1)
|
|
goto error;
|
|
|
|
return fd;
|
|
error:
|
|
if (fd != -1) {
|
|
close(fd);
|
|
fd = -1;
|
|
}
|
|
return fd;
|
|
}
|
|
|
|
/* mmap a startup-logs from a <fd>.
|
|
* if <new> is set to one, initialize the buffer.
|
|
* Returns the ring.
|
|
*/
|
|
static struct ring *startup_logs_from_fd(int fd, int new)
|
|
{
|
|
char *area;
|
|
struct ring *r = NULL;
|
|
|
|
if (fd == -1)
|
|
goto error;
|
|
|
|
area = mmap(NULL, STARTUP_LOG_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
|
if (area == MAP_FAILED || area == NULL)
|
|
goto error;
|
|
|
|
if (new)
|
|
r = ring_make_from_area(area, STARTUP_LOG_SIZE);
|
|
else
|
|
r = ring_cast_from_area(area);
|
|
|
|
if (r == NULL)
|
|
goto error;
|
|
|
|
shm_startup_logs = r; /* save the ptr so we can unmap later */
|
|
|
|
return r;
|
|
error:
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Use a shm across reexec of the master.
|
|
*
|
|
* During the startup of the master, a shm_open must be done and the FD saved
|
|
* into the HAPROXY_STARTUPLOGS_FD environment variable.
|
|
*
|
|
* When forking workers, the child must use a copy of the shm, not the shm itself.
|
|
*
|
|
* Once in wait mode, the shm must be copied and closed.
|
|
*
|
|
*/
|
|
void startup_logs_init()
|
|
{
|
|
struct ring *r = NULL;
|
|
char *str_fd, *endptr;
|
|
int fd = -1;
|
|
|
|
str_fd = getenv("HAPROXY_STARTUPLOGS_FD");
|
|
if (str_fd) {
|
|
fd = strtol(str_fd, &endptr, 10);
|
|
if (*endptr != '\0')
|
|
goto error;
|
|
unsetenv("HAPROXY_STARTUPLOGS_FD");
|
|
}
|
|
|
|
/* during startup, or just after a reload.
|
|
* Note: the WAIT_ONLY env variable must be
|
|
* check in case of an early call */
|
|
if (!(global.mode & MODE_MWORKER_WAIT) &&
|
|
getenv("HAPROXY_MWORKER_WAIT_ONLY") == NULL) {
|
|
if (fd != -1)
|
|
close(fd);
|
|
|
|
fd = startup_logs_new_shm();
|
|
if (fd == -1)
|
|
goto error;
|
|
|
|
r = startup_logs_from_fd(fd, 1);
|
|
if (!r)
|
|
goto error;
|
|
|
|
str_fd = NULL;
|
|
memprintf(&str_fd, "%d", fd);
|
|
setenv("HAPROXY_STARTUPLOGS_FD", str_fd, 1);
|
|
ha_free(&str_fd);
|
|
|
|
} else {
|
|
/* in wait mode, copy the shm to an allocated buffer */
|
|
struct ring *prev = NULL;
|
|
|
|
if (fd == -1)
|
|
goto error;
|
|
|
|
prev = startup_logs_from_fd(fd, 0);
|
|
if (!prev)
|
|
goto error;
|
|
|
|
r = startup_logs_dup(prev);
|
|
if (!r)
|
|
goto error;
|
|
startup_logs_free(prev);
|
|
close(fd);
|
|
}
|
|
|
|
startup_logs = r;
|
|
|
|
return;
|
|
error:
|
|
if (fd != -1)
|
|
close(fd);
|
|
/* couldn't get a mmap to work */
|
|
startup_logs = ring_new(STARTUP_LOG_SIZE);
|
|
|
|
}
|
|
|
|
#else /* ! USE_SHM_OPEN */
|
|
|
|
void startup_logs_init()
|
|
{
|
|
startup_logs = ring_new(STARTUP_LOG_SIZE);
|
|
}
|
|
|
|
#endif
|
|
|
|
/* free the startup logs, unmap if it was an shm */
|
|
void startup_logs_free(struct ring *r)
|
|
{
|
|
#ifdef USE_SHM_OPEN
|
|
if (r == shm_startup_logs)
|
|
munmap(r, STARTUP_LOG_SIZE);
|
|
else
|
|
#endif /* ! USE_SHM_OPEN */
|
|
ring_free(r);
|
|
}
|
|
|
|
/* duplicate a startup logs which was previously allocated in a shm */
|
|
struct ring *startup_logs_dup(struct ring *src)
|
|
{
|
|
struct ring *dst = NULL;
|
|
|
|
/* must use the size of the previous buffer */
|
|
dst = ring_new(b_size(&src->buf));
|
|
if (!dst)
|
|
goto error;
|
|
|
|
b_reset(&dst->buf);
|
|
b_ncat(&dst->buf, &src->buf, b_data(&src->buf));
|
|
error:
|
|
return dst;
|
|
}
|
|
|
|
/* Put msg in usermsgs_buf.
|
|
*
|
|
* The message should not be terminated by a newline because this function
|
|
* manually insert it.
|
|
*
|
|
* If there is not enough room in the buffer, the message is silently discarded.
|
|
* Do not forget to frequently clear the buffer.
|
|
*/
|
|
static void usermsgs_put(const struct ist *msg)
|
|
{
|
|
/* Allocate the buffer if not already done. */
|
|
if (unlikely(b_is_null(&usermsgs_buf))) {
|
|
usermsgs_buf.area = malloc(USER_MESSAGES_BUFSIZE * sizeof(char));
|
|
if (usermsgs_buf.area)
|
|
usermsgs_buf.size = USER_MESSAGES_BUFSIZE;
|
|
}
|
|
|
|
if (likely(!b_is_null(&usermsgs_buf))) {
|
|
if (b_room(&usermsgs_buf) >= msg->len + 2) {
|
|
/* Insert the message + newline. */
|
|
b_putblk(&usermsgs_buf, msg->ptr, msg->len);
|
|
b_putchr(&usermsgs_buf, '\n');
|
|
/* Insert NUL outside of the buffer. */
|
|
*b_tail(&usermsgs_buf) = '\0';
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Clear the user messages log buffer.
|
|
*
|
|
* <prefix> will set the local-thread context appended to every output
|
|
* following this call. It can be NULL if not necessary.
|
|
*/
|
|
void usermsgs_clr(const char *prefix)
|
|
{
|
|
if (likely(!b_is_null(&usermsgs_buf))) {
|
|
b_reset(&usermsgs_buf);
|
|
usermsgs_buf.area[0] = '\0';
|
|
}
|
|
|
|
usermsgs_ctx.prefix = prefix;
|
|
}
|
|
|
|
/* Check if the user messages buffer is empty. */
|
|
int usermsgs_empty(void)
|
|
{
|
|
return !!(b_is_null(&usermsgs_buf) || !b_data(&usermsgs_buf));
|
|
}
|
|
|
|
/* Return the messages log buffer content. */
|
|
const char *usermsgs_str(void)
|
|
{
|
|
if (unlikely(b_is_null(&usermsgs_buf)))
|
|
return "";
|
|
|
|
return b_head(&usermsgs_buf);
|
|
}
|
|
|
|
/* Set thread-local context infos to prefix forthcoming stderr output during
|
|
* configuration parsing.
|
|
*
|
|
* <file> and <line> specify the location of the parsed configuration.
|
|
*
|
|
* <obj> can be of various types. If not NULL, the string prefix generated will
|
|
* depend on its type.
|
|
*/
|
|
void set_usermsgs_ctx(const char *file, int line, enum obj_type *obj)
|
|
{
|
|
usermsgs_ctx.file = file;
|
|
usermsgs_ctx.line = line;
|
|
usermsgs_ctx.obj = obj;
|
|
}
|
|
|
|
/* Set thread-local context infos to prefix forthcoming stderr output. It will
|
|
* be set as a complement to possibly already defined file/line.
|
|
*
|
|
* <obj> can be of various types. If not NULL, the string prefix generated will
|
|
* depend on its type.
|
|
*/
|
|
void register_parsing_obj(enum obj_type *obj)
|
|
{
|
|
usermsgs_ctx.obj = obj;
|
|
}
|
|
|
|
/* Reset thread-local context infos for stderr output. */
|
|
void reset_usermsgs_ctx(void)
|
|
{
|
|
usermsgs_ctx.file = NULL;
|
|
usermsgs_ctx.line = 0;
|
|
usermsgs_ctx.obj = NULL;
|
|
}
|
|
|
|
static void generate_usermsgs_ctx_str(void)
|
|
{
|
|
struct usermsgs_ctx *ctx = &usermsgs_ctx;
|
|
void *area;
|
|
int ret;
|
|
|
|
if (unlikely(b_is_null(&ctx->str))) {
|
|
area = calloc(USERMSGS_CTX_BUFSIZE, sizeof(*area));
|
|
if (area)
|
|
ctx->str = b_make(area, USERMSGS_CTX_BUFSIZE, 0, 0);
|
|
}
|
|
|
|
if (likely(!b_is_null(&ctx->str))) {
|
|
b_reset(&ctx->str);
|
|
|
|
if (ctx->prefix) {
|
|
ret = snprintf(b_tail(&ctx->str), b_room(&ctx->str),
|
|
"%s : ", ctx->prefix);
|
|
b_add(&ctx->str, MIN(ret, b_room(&ctx->str)));
|
|
}
|
|
|
|
if (ctx->file) {
|
|
ret = snprintf(b_tail(&ctx->str), b_room(&ctx->str),
|
|
"[%s:%d] : ", ctx->file, ctx->line);
|
|
b_add(&ctx->str, MIN(ret, b_room(&ctx->str)));
|
|
}
|
|
|
|
switch (obj_type(ctx->obj)) {
|
|
case OBJ_TYPE_SERVER:
|
|
ret = snprintf(b_tail(&ctx->str), b_room(&ctx->str),
|
|
"'server %s/%s' : ",
|
|
__objt_server(ctx->obj)->proxy->id,
|
|
__objt_server(ctx->obj)->id);
|
|
b_add(&ctx->str, MIN(ret, b_room(&ctx->str)));
|
|
break;
|
|
|
|
case OBJ_TYPE_NONE:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (!b_data(&ctx->str))
|
|
snprintf(b_tail(&ctx->str), b_room(&ctx->str), "%s", "");
|
|
}
|
|
}
|
|
|
|
/* Generic function to display messages prefixed by a label */
|
|
static void print_message(int use_usermsgs_ctx, const char *label, const char *fmt, va_list argp)
|
|
{
|
|
struct ist msg_ist = IST_NULL;
|
|
char *head, *parsing_str, *msg;
|
|
char prefix[11]; // '[' + 8 chars + ']' + 0.
|
|
|
|
*prefix = '[';
|
|
strncpy(prefix + 1, label, sizeof(prefix) - 2);
|
|
msg = prefix + strlen(prefix);
|
|
*msg++ = ']';
|
|
while (msg < prefix + sizeof(prefix) - 1)
|
|
*msg++ = ' ';
|
|
*msg = 0;
|
|
|
|
head = parsing_str = msg = NULL;
|
|
memprintf(&head, "%s (%u) : ", prefix, (uint)getpid());
|
|
memvprintf(&msg, fmt, argp);
|
|
|
|
/* trim the trailing '\n' */
|
|
msg_ist = ist(msg);
|
|
if (msg_ist.len > 0 && msg_ist.ptr[msg_ist.len - 1] == '\n')
|
|
msg_ist.len--;
|
|
|
|
if (use_usermsgs_ctx) {
|
|
generate_usermsgs_ctx_str();
|
|
parsing_str = b_head(&usermsgs_ctx.str);
|
|
reset_usermsgs_ctx();
|
|
}
|
|
else {
|
|
parsing_str = "";
|
|
}
|
|
|
|
if (global.mode & MODE_STARTING) {
|
|
if (unlikely(!startup_logs))
|
|
startup_logs_init();
|
|
|
|
if (likely(startup_logs)) {
|
|
struct ist m[3];
|
|
|
|
m[0] = ist(head);
|
|
m[1] = ist(parsing_str);
|
|
m[2] = msg_ist;
|
|
|
|
ring_write(startup_logs, ~0, 0, 0, m, 3);
|
|
}
|
|
}
|
|
else {
|
|
usermsgs_put(&msg_ist);
|
|
}
|
|
|
|
fprintf(stderr, "%s%s%s", head, parsing_str, msg);
|
|
fflush(stderr);
|
|
|
|
free(head);
|
|
free(msg);
|
|
}
|
|
|
|
static void print_message_args(int use_usermsgs_ctx, const char *label, const char *fmt, ...)
|
|
{
|
|
va_list argp;
|
|
va_start(argp, fmt);
|
|
print_message(use_usermsgs_ctx, label, fmt, argp);
|
|
va_end(argp);
|
|
}
|
|
|
|
/*
|
|
* Displays the message on stderr with the pid. Overrides the quiet
|
|
* mode during startup.
|
|
*/
|
|
void ha_alert(const char *fmt, ...)
|
|
{
|
|
va_list argp;
|
|
|
|
if (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE) ||
|
|
!(global.mode & MODE_STARTING)) {
|
|
if (!(warned & WARN_EXEC_PATH) && (global.mode & MODE_STARTING)) {
|
|
const char *path = get_exec_path();
|
|
|
|
warned |= WARN_EXEC_PATH;
|
|
print_message_args(0, "NOTICE", "haproxy version is %s\n", haproxy_version);
|
|
if (path)
|
|
print_message_args(0, "NOTICE", "path to executable is %s\n", path);
|
|
}
|
|
va_start(argp, fmt);
|
|
print_message(1, "ALERT", fmt, argp);
|
|
va_end(argp);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Displays the message on stderr with the pid.
|
|
*/
|
|
void ha_warning(const char *fmt, ...)
|
|
{
|
|
va_list argp;
|
|
|
|
warned |= WARN_ANY;
|
|
HA_ATOMIC_INC(&tot_warnings);
|
|
|
|
if (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE) ||
|
|
!(global.mode & MODE_STARTING)) {
|
|
if (!(warned & WARN_EXEC_PATH) && (global.mode & MODE_STARTING)) {
|
|
const char *path = get_exec_path();
|
|
|
|
warned |= WARN_EXEC_PATH;
|
|
print_message_args(0, "NOTICE", "haproxy version is %s\n", haproxy_version);
|
|
if (path)
|
|
print_message_args(0, "NOTICE", "path to executable is %s\n", path);
|
|
}
|
|
va_start(argp, fmt);
|
|
print_message(1, "WARNING", fmt, argp);
|
|
va_end(argp);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Variant of _ha_diag_warning with va_list.
|
|
* Use it only if MODE_DIAG has been previously checked.
|
|
*/
|
|
void _ha_vdiag_warning(const char *fmt, va_list argp)
|
|
{
|
|
print_message(1, "DIAG", fmt, argp);
|
|
}
|
|
|
|
/*
|
|
* Output a diagnostic warning.
|
|
* Use it only if MODE_DIAG has been previously checked.
|
|
*/
|
|
void _ha_diag_warning(const char *fmt, ...)
|
|
{
|
|
va_list argp;
|
|
|
|
va_start(argp, fmt);
|
|
_ha_vdiag_warning(fmt, argp);
|
|
va_end(argp);
|
|
}
|
|
|
|
/*
|
|
* Output a diagnostic warning. Do nothing of MODE_DIAG is not on.
|
|
*/
|
|
void ha_diag_warning(const char *fmt, ...)
|
|
{
|
|
va_list argp;
|
|
|
|
if (global.mode & MODE_DIAG) {
|
|
va_start(argp, fmt);
|
|
_ha_vdiag_warning(fmt, argp);
|
|
va_end(argp);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Displays the message on stderr with the pid.
|
|
*/
|
|
void ha_notice(const char *fmt, ...)
|
|
{
|
|
va_list argp;
|
|
|
|
if (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE) ||
|
|
!(global.mode & MODE_STARTING)) {
|
|
va_start(argp, fmt);
|
|
print_message(1, "NOTICE", fmt, argp);
|
|
va_end(argp);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Displays the message on <out> only if quiet mode is not set.
|
|
*/
|
|
void qfprintf(FILE *out, const char *fmt, ...)
|
|
{
|
|
va_list argp;
|
|
|
|
if (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE)) {
|
|
va_start(argp, fmt);
|
|
vfprintf(out, fmt, argp);
|
|
fflush(out);
|
|
va_end(argp);
|
|
}
|
|
}
|
|
|
|
|
|
/* parse the "show startup-logs" command, returns 1 if a message is returned, otherwise zero */
|
|
static int cli_parse_show_startup_logs(char **args, char *payload, struct appctx *appctx, void *private)
|
|
{
|
|
if (!cli_has_level(appctx, ACCESS_LVL_OPER))
|
|
return 1;
|
|
|
|
if (!startup_logs)
|
|
return cli_msg(appctx, LOG_INFO, "\n"); // nothing to print
|
|
|
|
return ring_attach_cli(startup_logs, appctx, 0);
|
|
}
|
|
|
|
/* register cli keywords */
|
|
static struct cli_kw_list cli_kws = {{ },{
|
|
{ { "show", "startup-logs", NULL }, "show startup-logs : report logs emitted during HAProxy startup", cli_parse_show_startup_logs, NULL, NULL, NULL, ACCESS_MASTER },
|
|
{{},}
|
|
}};
|
|
|
|
INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
|
|
|
|
|
|
static void deinit_errors_buffers()
|
|
{
|
|
ring_free(_HA_ATOMIC_XCHG(&startup_logs, NULL));
|
|
ha_free(&usermsgs_buf.area);
|
|
ha_free(&usermsgs_ctx.str.area);
|
|
}
|
|
|
|
/* errors might be used in threads and even before forking, thus 2 deinit */
|
|
REGISTER_PER_THREAD_FREE(deinit_errors_buffers);
|
|
REGISTER_POST_DEINIT(deinit_errors_buffers);
|