/*
 * Copyright (c) 1997-8,2019,2021 Andrew G Morgan <morgan@kernel.org>
 *
 * This file deals with allocation and deallocation of internal
 * capability sets as specified by POSIX.1e (formerlly, POSIX 6).
 */

#include "libcap.h"

/*
 * Make start up atomic.
 */
static __u8 __libcap_mutex;

/*
 * These get set via the pre-main() executed constructor function below it.
 */
static cap_value_t _cap_max_bits;

__attribute__((visibility ("hidden")))
__attribute__((constructor (300))) void _libcap_initialize(void)
{
    int errno_saved = errno;
    _cap_mu_lock(&__libcap_mutex);
    if (!_cap_max_bits) {
	cap_set_syscall(NULL, NULL);
	_binary_search(_cap_max_bits, cap_get_bound, 0, __CAP_MAXBITS,
		       __CAP_BITS);
    }
    _cap_mu_unlock(&__libcap_mutex);
    errno = errno_saved;
}

cap_value_t cap_max_bits(void)
{
    return _cap_max_bits;
}

/*
 * capability allocation is all done in terms of this structure.
 */
struct _cap_alloc_s {
    __u32 magic;
    __u32 size;
    union {
	struct _cap_struct set;
	struct cap_iab_s iab;
	struct cap_launch_s launcher;
    } u;
};

/*
 * Obtain a blank set of capabilities
 */
cap_t cap_init(void)
{
    struct _cap_alloc_s *raw_data;
    cap_t result;

    raw_data = calloc(1, sizeof(struct _cap_alloc_s));
    if (raw_data == NULL) {
	_cap_debug("out of memory");
	errno = ENOMEM;
	return NULL;
    }
    raw_data->magic = CAP_T_MAGIC;
    raw_data->size = sizeof(struct _cap_alloc_s);

    result = &raw_data->u.set;
    result->head.version = _LIBCAP_CAPABILITY_VERSION;
    capget(&result->head, NULL);      /* load the kernel-capability version */

    switch (result->head.version) {
#ifdef _LINUX_CAPABILITY_VERSION_1
    case _LINUX_CAPABILITY_VERSION_1:
	break;
#endif
#ifdef _LINUX_CAPABILITY_VERSION_2
    case _LINUX_CAPABILITY_VERSION_2:
	break;
#endif
#ifdef _LINUX_CAPABILITY_VERSION_3
    case _LINUX_CAPABILITY_VERSION_3:
	break;
#endif
    default:                          /* No idea what to do */
	cap_free(result);
	result = NULL;
	break;
    }

    return result;
}

/*
 * This is an internal library function to duplicate a string and
 * tag the result as something cap_free can handle.
 */
__attribute__((visibility ("hidden"))) char *_libcap_strdup(const char *old)
{
    struct _cap_alloc_s *header;
    char *raw_data;
    size_t len;

    if (old == NULL) {
	errno = EINVAL;
	return NULL;
    }
    len = strlen(old) + 1 + 2*sizeof(__u32);
    if (len < sizeof(struct _cap_alloc_s)) {
	len = sizeof(struct _cap_alloc_s);
    }
    if ((len & 0xffffffff) != len) {
	_cap_debug("len is too long for libcap to manage");
	errno = EINVAL;
	return NULL;
    }

    raw_data = calloc(1, len);
    if (raw_data == NULL) {
	errno = ENOMEM;
	return NULL;
    }
    header = (void *) raw_data;
    header->magic = CAP_S_MAGIC;
    header->size = (__u32) len;

    raw_data += 2*sizeof(__u32);
    strcpy(raw_data, old);
    return raw_data;
}

/*
 * This function duplicates an internal capability set with
 * calloc()'d memory. It is the responsibility of the user to call
 * cap_free() to liberate it.
 */
cap_t cap_dup(cap_t cap_d)
{
    cap_t result;

    if (!good_cap_t(cap_d)) {
	_cap_debug("bad argument");
	errno = EINVAL;
	return NULL;
    }

    result = cap_init();
    if (result == NULL) {
	_cap_debug("out of memory");
	return NULL;
    }

    _cap_mu_lock(&cap_d->mutex);
    memcpy(result, cap_d, sizeof(*cap_d));
    _cap_mu_unlock(&cap_d->mutex);
    _cap_mu_unlock(&result->mutex);

    return result;
}

cap_iab_t cap_iab_init(void)
{
    struct _cap_alloc_s *base = calloc(1, sizeof(struct _cap_alloc_s));
    if (base == NULL) {
	_cap_debug("out of memory");
	return NULL;
    }
    base->magic = CAP_IAB_MAGIC;
    base->size = sizeof(struct _cap_alloc_s);
    return &base->u.iab;
}

/*
 * This function duplicates an internal iab tuple with calloc()'d
 * memory. It is the responsibility of the user to call cap_free() to
 * liberate it.
 */
cap_iab_t cap_iab_dup(cap_iab_t iab)
{
    cap_iab_t result;

    if (!good_cap_iab_t(iab)) {
	_cap_debug("bad argument");
	errno = EINVAL;
	return NULL;
    }

    result = cap_iab_init();
    if (result == NULL) {
	_cap_debug("out of memory");
	return NULL;
    }

    _cap_mu_lock(&iab->mutex);
    memcpy(result, iab, sizeof(*iab));
    _cap_mu_unlock(&iab->mutex);
    _cap_mu_unlock(&result->mutex);

    return result;
}

/*
 * cap_new_launcher allocates some memory for a launcher and
 * initializes it.  To actually launch a program with this launcher,
 * use cap_launch(). By default, the launcher is a no-op from a
 * security perspective and will act just as fork()/execve()
 * would. Use cap_launcher_setuid() etc to override this.
 */
cap_launch_t cap_new_launcher(const char *arg0, const char * const *argv,
			      const char * const *envp)
{
    struct _cap_alloc_s *data = calloc(1, sizeof(struct _cap_alloc_s));
    if (data == NULL) {
	_cap_debug("out of memory");
	return NULL;
    }
    data->magic = CAP_LAUNCH_MAGIC;
    data->size = sizeof(struct _cap_alloc_s);

    struct cap_launch_s *attr = &data->u.launcher;
    attr->arg0 = arg0;
    attr->argv = argv;
    attr->envp = envp;
    return attr;
}

/*
 * cap_func_launcher allocates some memory for a launcher and
 * initializes it. The purpose of this launcher, unlike one created
 * with cap_new_launcher(), is to execute some function code from a
 * forked copy of the program. The forked process will exit when the
 * callback function, func, returns.
 */
cap_launch_t cap_func_launcher(int (callback_fn)(void *detail))
{
    struct _cap_alloc_s *data = calloc(1, sizeof(struct _cap_alloc_s));
    if (data == NULL) {
	_cap_debug("out of memory");
	return NULL;
    }
    data->magic = CAP_LAUNCH_MAGIC;
    data->size = sizeof(struct _cap_alloc_s);

    struct cap_launch_s *attr = &data->u.launcher;
    attr->custom_setup_fn = callback_fn;
    return attr;
}

/*
 * Scrub and then liberate the recognized allocated object.
 */
int cap_free(void *data_p)
{
    if (!data_p) {
	return 0;
    }

    /* confirm alignment */
    if ((sizeof(uintptr_t)-1) & (uintptr_t) data_p) {
	_cap_debug("whatever we're cap_free()ing it isn't aligned right: %p",
		   data_p);
	errno = EINVAL;
	return -1;
    }

    void *base = (void *) (-2 + (__u32 *) data_p);
    struct _cap_alloc_s *data = base;
    switch (data->magic) {
    case CAP_T_MAGIC:
	_cap_mu_lock(&data->u.set.mutex);
	break;
    case CAP_S_MAGIC:
    case CAP_IAB_MAGIC:
	break;
    case CAP_LAUNCH_MAGIC:
	if (data->u.launcher.iab != NULL) {
	    _cap_mu_unlock(&data->u.launcher.iab->mutex);
	    if (cap_free(data->u.launcher.iab) != 0) {
		return -1;
	    }
	}
	data->u.launcher.iab = NULL;
	if (cap_free(data->u.launcher.chroot) != 0) {
	    return -1;
	}
	data->u.launcher.chroot = NULL;
	break;
    default:
	_cap_debug("don't recognize what we're supposed to liberate");
	errno = EINVAL;
	return -1;
    }

    /*
     * operate here with respect to base, to avoid tangling with the
     * automated buffer overflow detection.
     */
    memset(base, 0, data->size);
    free(base);
    data_p = NULL;
    data = NULL;
    base = NULL;
    return 0;
}
