de4a1ea684
* src/engine-spawn.c (engspawn_start): Allocate space for list terminator. * src/posix-util.c (walk_path): Fix trailing slash detection. -- Kudos to Valgrind for pointing out these two problems. The first is a plain allocation bug in a code pattern I have written thousands of times - this time it went wrong. The allocation is not user controlled thus not directly exploitable. The second is missed to do what it intended to do. Found due to the access of malloced but not initialized memory. Not using calloc again proved to be helpful to detect logical error.
476 lines
10 KiB
C
476 lines
10 KiB
C
/* engine-spawn.c - Run an arbitrary program
|
||
Copyright (C) 2014 g10 Code GmbH
|
||
|
||
This file is part of GPGME.
|
||
|
||
GPGME is free software; you can redistribute it and/or modify it
|
||
under the terms of the GNU Lesser General Public License as
|
||
published by the Free Software Foundation; either version 2.1 of
|
||
the License, or (at your option) any later version.
|
||
|
||
GPGME is distributed in the hope that it will be useful, but
|
||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
Lesser General Public License for more details.
|
||
|
||
You should have received a copy of the GNU Lesser General Public
|
||
License along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||
*/
|
||
|
||
#if HAVE_CONFIG_H
|
||
#include <config.h>
|
||
#endif
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <assert.h>
|
||
#include <errno.h>
|
||
#ifdef HAVE_UNISTD_H
|
||
# include <unistd.h>
|
||
#endif
|
||
#ifdef HAVE_LOCALE_H
|
||
#include <locale.h>
|
||
#endif
|
||
|
||
#include "gpgme.h"
|
||
#include "util.h"
|
||
#include "ops.h"
|
||
#include "wait.h"
|
||
#include "context.h" /*temp hack until we have GpmeData methods to do I/O */
|
||
#include "priv-io.h"
|
||
#include "sema.h"
|
||
#include "debug.h"
|
||
|
||
#include "engine-backend.h"
|
||
|
||
|
||
/* This type is used to build a list of data sources/sinks. */
|
||
struct datalist_s
|
||
{
|
||
struct datalist_s *next;
|
||
gpgme_data_t data; /* The data object. */
|
||
int inbound; /* True if this is used for reading from the peer. */
|
||
int dup_to; /* The fd used by the peer. */
|
||
};
|
||
|
||
|
||
struct fd_data_map_s
|
||
{
|
||
gpgme_data_t data;
|
||
int inbound; /* True if this is used for reading from the peer. */
|
||
int dup_to; /* Dup the fd to that one. */
|
||
int fd; /* The fd to use. */
|
||
int peer_fd; /* The other side of the pipe. */
|
||
void *tag; /* Tag used by the I/O callback. */
|
||
};
|
||
|
||
|
||
struct engine_spawn
|
||
{
|
||
struct datalist_s *arglist;
|
||
struct datalist_s **argtail;
|
||
|
||
struct fd_data_map_s *fd_data_map;
|
||
|
||
struct gpgme_io_cbs io_cbs;
|
||
};
|
||
typedef struct engine_spawn *engine_spawn_t;
|
||
|
||
|
||
static void engspawn_io_event (void *engine,
|
||
gpgme_event_io_t type, void *type_data);
|
||
static gpgme_error_t engspawn_cancel (void *engine);
|
||
|
||
|
||
|
||
static void
|
||
close_notify_handler (int fd, void *opaque)
|
||
{
|
||
engine_spawn_t esp = opaque;
|
||
int i;
|
||
|
||
assert (fd != -1);
|
||
|
||
if (esp->fd_data_map)
|
||
{
|
||
for (i = 0; esp->fd_data_map[i].data; i++)
|
||
{
|
||
if (esp->fd_data_map[i].fd == fd)
|
||
{
|
||
if (esp->fd_data_map[i].tag)
|
||
(*esp->io_cbs.remove) (esp->fd_data_map[i].tag);
|
||
esp->fd_data_map[i].fd = -1;
|
||
break;
|
||
}
|
||
if (esp->fd_data_map[i].peer_fd == fd)
|
||
{
|
||
esp->fd_data_map[i].peer_fd = -1;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
static gpgme_error_t
|
||
add_data (engine_spawn_t esp, gpgme_data_t data, int dup_to, int inbound)
|
||
{
|
||
struct datalist_s *a;
|
||
|
||
assert (esp);
|
||
assert (data);
|
||
|
||
a = malloc (sizeof *a - 1);
|
||
if (!a)
|
||
return gpg_error_from_syserror ();
|
||
a->next = NULL;
|
||
a->data = data;
|
||
a->inbound = inbound;
|
||
a->dup_to = dup_to;
|
||
*esp->argtail = a;
|
||
esp->argtail = &a->next;
|
||
return 0;
|
||
}
|
||
|
||
|
||
static void
|
||
free_fd_data_map (struct fd_data_map_s *fd_data_map)
|
||
{
|
||
int i;
|
||
|
||
if (!fd_data_map)
|
||
return;
|
||
|
||
for (i = 0; fd_data_map[i].data; i++)
|
||
{
|
||
if (fd_data_map[i].fd != -1)
|
||
_gpgme_io_close (fd_data_map[i].fd);
|
||
if (fd_data_map[i].peer_fd != -1)
|
||
_gpgme_io_close (fd_data_map[i].peer_fd);
|
||
/* Don't release data because this is only a reference. */
|
||
}
|
||
free (fd_data_map);
|
||
}
|
||
|
||
|
||
static gpgme_error_t
|
||
build_fd_data_map (engine_spawn_t esp)
|
||
{
|
||
struct datalist_s *a;
|
||
size_t datac;
|
||
int fds[2];
|
||
|
||
for (datac = 0, a = esp->arglist; a; a = a->next)
|
||
if (a->data)
|
||
datac++;
|
||
|
||
free_fd_data_map (esp->fd_data_map);
|
||
esp->fd_data_map = calloc (datac + 1, sizeof *esp->fd_data_map);
|
||
if (!esp->fd_data_map)
|
||
return gpg_error_from_syserror ();
|
||
|
||
for (datac = 0, a = esp->arglist; a; a = a->next)
|
||
{
|
||
assert (a->data);
|
||
|
||
if (_gpgme_io_pipe (fds, a->inbound ? 1 : 0) == -1)
|
||
{
|
||
free (esp->fd_data_map);
|
||
esp->fd_data_map = NULL;
|
||
return gpg_error_from_syserror ();
|
||
}
|
||
if (_gpgme_io_set_close_notify (fds[0], close_notify_handler, esp)
|
||
|| _gpgme_io_set_close_notify (fds[1], close_notify_handler, esp))
|
||
{
|
||
/* FIXME: Need error cleanup. */
|
||
return gpg_error (GPG_ERR_GENERAL);
|
||
}
|
||
|
||
esp->fd_data_map[datac].inbound = a->inbound;
|
||
if (a->inbound)
|
||
{
|
||
esp->fd_data_map[datac].fd = fds[0];
|
||
esp->fd_data_map[datac].peer_fd = fds[1];
|
||
}
|
||
else
|
||
{
|
||
esp->fd_data_map[datac].fd = fds[1];
|
||
esp->fd_data_map[datac].peer_fd = fds[0];
|
||
}
|
||
esp->fd_data_map[datac].data = a->data;
|
||
esp->fd_data_map[datac].dup_to = a->dup_to;
|
||
datac++;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
|
||
static gpgme_error_t
|
||
add_io_cb (engine_spawn_t esp, int fd, int dir, gpgme_io_cb_t handler,
|
||
void *data, void **tag)
|
||
{
|
||
gpgme_error_t err;
|
||
|
||
err = (*esp->io_cbs.add) (esp->io_cbs.add_priv, fd, dir, handler, data, tag);
|
||
if (err)
|
||
return err;
|
||
if (!dir) /* Fixme: Kludge around poll() problem. */
|
||
err = _gpgme_io_set_nonblocking (fd);
|
||
return err;
|
||
}
|
||
|
||
|
||
static gpgme_error_t
|
||
engspawn_start (engine_spawn_t esp, const char *file, const char *argv[],
|
||
unsigned int flags)
|
||
{
|
||
gpgme_error_t err;
|
||
int i, n;
|
||
int status;
|
||
struct spawn_fd_item_s *fd_list;
|
||
pid_t pid;
|
||
unsigned int spflags;
|
||
const char *save_argv0 = NULL;
|
||
|
||
if (!esp || !file || !argv || !argv[0])
|
||
return gpg_error (GPG_ERR_INV_VALUE);
|
||
|
||
spflags = 0;
|
||
if ((flags & GPGME_SPAWN_DETACHED))
|
||
spflags |= IOSPAWN_FLAG_DETACHED;
|
||
if ((flags & GPGME_SPAWN_ALLOW_SET_FG))
|
||
spflags |= IOSPAWN_FLAG_ALLOW_SET_FG;
|
||
|
||
|
||
err = build_fd_data_map (esp);
|
||
if (err)
|
||
return err;
|
||
|
||
n = 0;
|
||
for (i = 0; esp->fd_data_map[i].data; i++)
|
||
n++;
|
||
fd_list = calloc (n+1, sizeof *fd_list);
|
||
if (!fd_list)
|
||
return gpg_error_from_syserror ();
|
||
|
||
/* Build the fd list for the child. */
|
||
n = 0;
|
||
for (i = 0; esp->fd_data_map[i].data; i++)
|
||
{
|
||
fd_list[n].fd = esp->fd_data_map[i].peer_fd;
|
||
fd_list[n].dup_to = esp->fd_data_map[i].dup_to;
|
||
n++;
|
||
}
|
||
fd_list[n].fd = -1;
|
||
fd_list[n].dup_to = -1;
|
||
|
||
if (argv[0] && !*argv[0])
|
||
{
|
||
save_argv0 = argv[0];
|
||
argv[0] = _gpgme_get_basename (file);
|
||
}
|
||
status = _gpgme_io_spawn (file, (char * const *)argv, spflags,
|
||
fd_list, NULL, NULL, &pid);
|
||
if (save_argv0)
|
||
argv[0] = save_argv0;
|
||
free (fd_list);
|
||
if (status == -1)
|
||
return gpg_error_from_syserror ();
|
||
|
||
for (i = 0; esp->fd_data_map[i].data; i++)
|
||
{
|
||
err = add_io_cb (esp, esp->fd_data_map[i].fd,
|
||
esp->fd_data_map[i].inbound,
|
||
esp->fd_data_map[i].inbound
|
||
? _gpgme_data_inbound_handler
|
||
: _gpgme_data_outbound_handler,
|
||
esp->fd_data_map[i].data, &esp->fd_data_map[i].tag);
|
||
if (err)
|
||
return err; /* FIXME: kill the child */
|
||
}
|
||
|
||
engspawn_io_event (esp, GPGME_EVENT_START, NULL);
|
||
|
||
return 0;
|
||
}
|
||
|
||
|
||
|
||
/*
|
||
Public functions
|
||
*/
|
||
|
||
static const char *
|
||
engspawn_get_file_name (void)
|
||
{
|
||
return "/nonexistent";
|
||
}
|
||
|
||
|
||
static char *
|
||
engspawn_get_version (const char *file_name)
|
||
{
|
||
(void)file_name;
|
||
return strdup ("1.0");
|
||
}
|
||
|
||
|
||
static const char *
|
||
engspawn_get_req_version (void)
|
||
{
|
||
return "1.0";
|
||
}
|
||
|
||
|
||
static gpgme_error_t
|
||
engspawn_new (void **engine, const char *file_name, const char *home_dir)
|
||
{
|
||
engine_spawn_t esp;
|
||
|
||
(void)file_name;
|
||
(void)home_dir;
|
||
|
||
esp = calloc (1, sizeof *esp);
|
||
if (!esp)
|
||
return gpg_error_from_syserror ();
|
||
|
||
esp->argtail = &esp->arglist;
|
||
*engine = esp;
|
||
return 0;
|
||
}
|
||
|
||
|
||
static void
|
||
engspawn_release (void *engine)
|
||
{
|
||
engine_spawn_t esp = engine;
|
||
|
||
if (!esp)
|
||
return;
|
||
|
||
engspawn_cancel (engine);
|
||
|
||
while (esp->arglist)
|
||
{
|
||
struct datalist_s *next = esp->arglist->next;
|
||
|
||
if (esp->arglist)
|
||
free (esp->arglist);
|
||
esp->arglist = next;
|
||
}
|
||
|
||
free (esp);
|
||
}
|
||
|
||
|
||
static void
|
||
engspawn_set_io_cbs (void *engine, gpgme_io_cbs_t io_cbs)
|
||
{
|
||
engine_spawn_t esp = engine;
|
||
|
||
esp->io_cbs = *io_cbs;
|
||
}
|
||
|
||
|
||
static void
|
||
engspawn_io_event (void *engine, gpgme_event_io_t type, void *type_data)
|
||
{
|
||
engine_spawn_t esp = engine;
|
||
|
||
TRACE3 (DEBUG_ENGINE, "gpgme:engspawn_io_event", esp,
|
||
"event %p, type %d, type_data %p",
|
||
esp->io_cbs.event, type, type_data);
|
||
if (esp->io_cbs.event)
|
||
(*esp->io_cbs.event) (esp->io_cbs.event_priv, type, type_data);
|
||
}
|
||
|
||
|
||
static gpgme_error_t
|
||
engspawn_cancel (void *engine)
|
||
{
|
||
engine_spawn_t esp = engine;
|
||
|
||
if (!esp)
|
||
return gpg_error (GPG_ERR_INV_VALUE);
|
||
|
||
if (esp->fd_data_map)
|
||
{
|
||
free_fd_data_map (esp->fd_data_map);
|
||
esp->fd_data_map = NULL;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
|
||
static gpgme_error_t
|
||
engspawn_op_spawn (void *engine,
|
||
const char *file, const char *argv[],
|
||
gpgme_data_t datain,
|
||
gpgme_data_t dataout, gpgme_data_t dataerr,
|
||
unsigned int flags)
|
||
{
|
||
engine_spawn_t esp = engine;
|
||
gpgme_error_t err = 0;
|
||
|
||
if (datain)
|
||
err = add_data (esp, datain, 0, 0);
|
||
if (!err && dataout)
|
||
err = add_data (esp, dataout, 1, 1);
|
||
if (!err && dataerr)
|
||
err = add_data (esp, dataerr, 2, 1);
|
||
|
||
if (!err)
|
||
err = engspawn_start (esp, file, argv, flags);
|
||
|
||
return err;
|
||
}
|
||
|
||
|
||
|
||
struct engine_ops _gpgme_engine_ops_spawn =
|
||
{
|
||
/* Static functions. */
|
||
engspawn_get_file_name,
|
||
NULL, /* get_home_dir */
|
||
engspawn_get_version,
|
||
engspawn_get_req_version,
|
||
engspawn_new,
|
||
|
||
/* Member functions. */
|
||
engspawn_release,
|
||
NULL, /* reset */
|
||
NULL, /* set_status_handler */
|
||
NULL, /* set_command_handler */
|
||
NULL, /* set_colon_line_handler */
|
||
NULL, /* set_locale */
|
||
NULL, /* set_protocol */
|
||
NULL, /* decrypt */
|
||
NULL, /* decrypt_verify */
|
||
NULL, /* delete */
|
||
NULL, /* edit */
|
||
NULL, /* encrypt */
|
||
NULL, /* encrypt_sign */
|
||
NULL, /* export */
|
||
NULL, /* export_ext */
|
||
NULL, /* genkey */
|
||
NULL, /* import */
|
||
NULL, /* keylist */
|
||
NULL, /* keylist_ext */
|
||
NULL, /* sign */
|
||
NULL, /* trustlist */
|
||
NULL, /* verify */
|
||
NULL, /* getauditlog */
|
||
NULL, /* opassuan_transact */
|
||
NULL, /* conf_load */
|
||
NULL, /* conf_save */
|
||
engspawn_set_io_cbs,
|
||
engspawn_io_event, /* io_event */
|
||
engspawn_cancel, /* cancel */
|
||
NULL, /* cancel_op */
|
||
NULL, /* passwd */
|
||
NULL, /* set_pinentry_mode */
|
||
engspawn_op_spawn /* opspawn */
|
||
};
|