root/lib/libsys/auxv.c
/*-
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright 2010, 2012 Konstantin Belousov <kib@FreeBSD.ORG>.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#include "namespace.h"
#include <elf.h>
#include <errno.h>
#include <link.h>
#include <pthread.h>
#include <stdbool.h>
#include <string.h>
#include <sys/auxv.h>
#include "un-namespace.h"
#include "libc_private.h"
#include <machine/atomic.h>

extern int _DYNAMIC;
#pragma weak _DYNAMIC

void *__elf_aux_vector;

#ifndef PIC
static pthread_once_t aux_vector_once = PTHREAD_ONCE_INIT;

static void
init_aux_vector_once(void)
{
        Elf_Addr *sp;

        sp = (Elf_Addr *)environ;
        while (*sp++ != 0)
                ;
        __elf_aux_vector = (Elf_Auxinfo *)sp;
}

void
__init_elf_aux_vector(void)
{

        if (&_DYNAMIC != NULL)
                return;
        _once(&aux_vector_once, init_aux_vector_once);
}
#endif

static int aux_once;
static int pagesize, osreldate, canary_len, ncpus, pagesizes_len, bsdflags;
static int hwcap_present, hwcap2_present, hwcap3_present, hwcap4_present;
static char *canary, *pagesizes, *execpath;
static void *ps_strings, *timekeep;
static u_long hwcap, hwcap2, hwcap3, hwcap4;
static void *fxrng_seed_version;
static u_long usrstackbase, usrstacklim;

#ifdef __powerpc__
static int powerpc_new_auxv_format = 0;
static void _init_aux_powerpc_fixup(void);
int _powerpc_elf_aux_info(int, void *, int);
#endif

/*
 * This function might be called and actual body executed more than
 * once in multithreading environment.  Due to this, it is and must
 * continue to be idempotent.  All stores are atomic (no store
 * tearing), because we only assign to int/long/ptr.
 */
static void
init_aux(void)
{
        Elf_Auxinfo *aux;

        if (atomic_load_acq_int(&aux_once))
                return;
        for (aux = __elf_aux_vector; aux->a_type != AT_NULL; aux++) {
                switch (aux->a_type) {
                case AT_BSDFLAGS:
                        bsdflags = aux->a_un.a_val;
                        break;

                case AT_CANARY:
                        canary = (char *)(aux->a_un.a_ptr);
                        break;

                case AT_CANARYLEN:
                        canary_len = aux->a_un.a_val;
                        break;

                case AT_EXECPATH:
                        execpath = (char *)(aux->a_un.a_ptr);
                        break;

                case AT_HWCAP:
                        hwcap_present = 1;
                        hwcap = (u_long)(aux->a_un.a_val);
                        break;

                case AT_HWCAP2:
                        hwcap2_present = 1;
                        hwcap2 = (u_long)(aux->a_un.a_val);
                        break;

                case AT_HWCAP3:
                        hwcap3_present = 1;
                        hwcap3 = (u_long)(aux->a_un.a_val);
                        break;

                case AT_HWCAP4:
                        hwcap4_present = 1;
                        hwcap4 = (u_long)(aux->a_un.a_val);
                        break;

                case AT_PAGESIZES:
                        pagesizes = (char *)(aux->a_un.a_ptr);
                        break;

                case AT_PAGESIZESLEN:
                        pagesizes_len = aux->a_un.a_val;
                        break;

                case AT_PAGESZ:
                        pagesize = aux->a_un.a_val;
                        break;

                case AT_OSRELDATE:
                        osreldate = aux->a_un.a_val;
                        break;

                case AT_NCPUS:
                        ncpus = aux->a_un.a_val;
                        break;

                case AT_TIMEKEEP:
                        timekeep = aux->a_un.a_ptr;
                        break;

                case AT_PS_STRINGS:
                        ps_strings = aux->a_un.a_ptr;
                        break;

                case AT_FXRNG:
                        fxrng_seed_version = aux->a_un.a_ptr;
                        break;

                case AT_USRSTACKBASE:
                        usrstackbase = aux->a_un.a_val;
                        break;

                case AT_USRSTACKLIM:
                        usrstacklim = aux->a_un.a_val;
                        break;
#ifdef __powerpc__
                /*
                 * Since AT_STACKPROT is always set, and the common
                 * value 23 is mutually exclusive with the legacy powerpc
                 * value 21, the existence of AT_STACKPROT proves we are
                 * on the common format.
                 */
                case AT_STACKPROT:      /* 23 */
                        powerpc_new_auxv_format = 1;
                        break;
#endif
                }
        }
#ifdef __powerpc__
        if (!powerpc_new_auxv_format)
                _init_aux_powerpc_fixup();
#endif

        atomic_store_rel_int(&aux_once, 1);
}

#ifdef __powerpc__
static void
_init_aux_powerpc_fixup(void)
{
        Elf_Auxinfo *aux;

        /*
         * Before 1300070, PowerPC platforms had nonstandard numbering for
         * the aux vector. When running old binaries, the kernel will pass
         * the vector using the old numbering. Reload affected variables.
         */
        for (aux = __elf_aux_vector; aux->a_type != AT_NULL; aux++) {
                switch (aux->a_type) {
                case AT_OLD_CANARY:
                        canary = (char *)(aux->a_un.a_ptr);
                        break;
                case AT_OLD_CANARYLEN:
                        canary_len = aux->a_un.a_val;
                        break;
                case AT_OLD_EXECPATH:
                        execpath = (char *)(aux->a_un.a_ptr);
                        break;
                case AT_OLD_PAGESIZES:
                        pagesizes = (char *)(aux->a_un.a_ptr);
                        break;
                case AT_OLD_PAGESIZESLEN:
                        pagesizes_len = aux->a_un.a_val;
                        break;
                case AT_OLD_OSRELDATE:
                        osreldate = aux->a_un.a_val;
                        break;
                case AT_OLD_NCPUS:
                        ncpus = aux->a_un.a_val;
                        break;
                }
        }
}

int
_powerpc_elf_aux_info(int aux, void *buf, int buflen)
{

        /*
         * If we are in the old auxv format, we need to translate the aux
         * parameter of elf_aux_info() calls into the common auxv format.
         * Internal libc calls always use the common format, and they
         * directly call _elf_aux_info instead of using the weak symbol.
         */
        if (!powerpc_new_auxv_format) {
                switch (aux) {
                case AT_OLD_EXECPATH:
                        aux = AT_EXECPATH;
                        break;
                case AT_OLD_CANARY:
                        aux = AT_CANARY;
                        break;
                case AT_OLD_CANARYLEN:
                        aux = AT_CANARYLEN;
                        break;
                case AT_OLD_OSRELDATE:
                        aux = AT_OSRELDATE;
                        break;
                case AT_OLD_NCPUS:
                        aux = AT_NCPUS;
                        break;
                case AT_OLD_PAGESIZES:
                        aux = AT_PAGESIZES;
                        break;
                case AT_OLD_PAGESIZESLEN:
                        aux = AT_PAGESIZESLEN;
                        break;
                case AT_OLD_STACKPROT:
                        aux = AT_STACKPROT;
                        break;
                }
        }
        return _elf_aux_info(aux, buf, buflen);
}
__weak_reference(_powerpc_elf_aux_info, elf_aux_info);
#else
__weak_reference(_elf_aux_info, elf_aux_info);
#endif

int
_elf_aux_info(int aux, void *buf, int buflen)
{
        int res;

#ifndef PIC
        __init_elf_aux_vector();
#endif
        if (__elf_aux_vector == NULL)
                return (ENOSYS);
        init_aux();     /* idempotent */

        if (buflen < 0)
                return (EINVAL);

        switch (aux) {
        case AT_CANARY:
                if (canary != NULL && canary_len >= buflen) {
                        memcpy(buf, canary, buflen);
                        memset(canary, 0, canary_len);
                        canary = NULL;
                        res = 0;
                } else
                        res = ENOENT;
                break;
        case AT_EXECPATH:
                if (execpath == NULL)
                        res = ENOENT;
                else if (buf == NULL)
                        res = EINVAL;
                else {
                        if (strlcpy(buf, execpath, buflen) >=
                            (unsigned int)buflen)
                                res = EINVAL;
                        else
                                res = 0;
                }
                break;
        case AT_HWCAP:
                if (hwcap_present && buflen == sizeof(u_long)) {
                        *(u_long *)buf = hwcap;
                        res = 0;
                } else
                        res = ENOENT;
                break;
        case AT_HWCAP2:
                if (hwcap2_present && buflen == sizeof(u_long)) {
                        *(u_long *)buf = hwcap2;
                        res = 0;
                } else
                        res = ENOENT;
                break;
        case AT_HWCAP3:
                if (hwcap3_present && buflen == sizeof(u_long)) {
                        *(u_long *)buf = hwcap3;
                        res = 0;
                } else
                        res = ENOENT;
                break;
        case AT_HWCAP4:
                if (hwcap4_present && buflen == sizeof(u_long)) {
                        *(u_long *)buf = hwcap4;
                        res = 0;
                } else
                        res = ENOENT;
                break;
        case AT_PAGESIZES:
                if (pagesizes != NULL && pagesizes_len >= buflen) {
                        memcpy(buf, pagesizes, buflen);
                        res = 0;
                } else
                        res = ENOENT;
                break;
        case AT_PAGESZ:
                if (buflen == sizeof(int)) {
                        if (pagesize != 0) {
                                *(int *)buf = pagesize;
                                res = 0;
                        } else
                                res = ENOENT;
                } else
                        res = EINVAL;
                break;
        case AT_OSRELDATE:
                if (buflen == sizeof(int)) {
                        if (osreldate != 0) {
                                *(int *)buf = osreldate;
                                res = 0;
                        } else
                                res = ENOENT;
                } else
                        res = EINVAL;
                break;
        case AT_NCPUS:
                if (buflen == sizeof(int)) {
                        if (ncpus != 0) {
                                *(int *)buf = ncpus;
                                res = 0;
                        } else
                                res = ENOENT;
                } else
                        res = EINVAL;
                break;
        case AT_TIMEKEEP:
                if (buflen == sizeof(void *)) {
                        if (timekeep != NULL) {
                                *(void **)buf = timekeep;
                                res = 0;
                        } else
                                res = ENOENT;
                } else
                        res = EINVAL;
                break;
        case AT_BSDFLAGS:
                if (buflen == sizeof(int)) {
                        *(int *)buf = bsdflags;
                        res = 0;
                } else
                        res = EINVAL;
                break;
        case AT_PS_STRINGS:
                if (buflen == sizeof(void *)) {
                        if (ps_strings != NULL) {
                                *(void **)buf = ps_strings;
                                res = 0;
                        } else
                                res = ENOENT;
                } else
                        res = EINVAL;
                break;
        case AT_FXRNG:
                if (buflen == sizeof(void *)) {
                        if (fxrng_seed_version != NULL) {
                                *(void **)buf = fxrng_seed_version;
                                res = 0;
                        } else
                                res = ENOENT;
                } else
                        res = EINVAL;
                break;
        case AT_USRSTACKBASE:
                if (buflen == sizeof(u_long)) {
                        if (usrstackbase != 0) {
                                *(u_long *)buf = usrstackbase;
                                res = 0;
                        } else
                                res = ENOENT;
                } else
                        res = EINVAL;
                break;
        case AT_USRSTACKLIM:
                if (buflen == sizeof(u_long)) {
                        if (usrstacklim != 0) {
                                *(u_long *)buf = usrstacklim;
                                res = 0;
                        } else
                                res = ENOENT;
                } else
                        res = EINVAL;
                break;
        default:
                res = ENOENT;
                break;
        }
        return (res);
}