cregit-Linux how code gets into the kernel

Release 4.14 arch/arm64/kernel/signal.c

/*
 * Based on arch/arm/kernel/signal.c
 *
 * Copyright (C) 1995-2009 Russell King
 * Copyright (C) 2012 ARM Ltd.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <linux/compat.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/signal.h>
#include <linux/personality.h>
#include <linux/freezer.h>
#include <linux/stddef.h>
#include <linux/uaccess.h>
#include <linux/sizes.h>
#include <linux/string.h>
#include <linux/tracehook.h>
#include <linux/ratelimit.h>
#include <linux/syscalls.h>

#include <asm/debug-monitors.h>
#include <asm/elf.h>
#include <asm/cacheflush.h>
#include <asm/ucontext.h>
#include <asm/unistd.h>
#include <asm/fpsimd.h>
#include <asm/ptrace.h>
#include <asm/signal32.h>
#include <asm/vdso.h>

/*
 * Do a signal return; undo the signal stack. These are aligned to 128-bit.
 */

struct rt_sigframe {
	
struct siginfo info;
	
struct ucontext uc;
};


struct frame_record {
	
u64 fp;
	
u64 lr;
};


struct rt_sigframe_user_layout {
	
struct rt_sigframe __user *sigframe;
	
struct frame_record __user *next_frame;

	
unsigned long size;	/* size of allocated sigframe data */
	
unsigned long limit;	/* largest allowed size */

	
unsigned long fpsimd_offset;
	
unsigned long esr_offset;
	
unsigned long extra_offset;
	
unsigned long end_offset;
};


#define BASE_SIGFRAME_SIZE round_up(sizeof(struct rt_sigframe), 16)

#define TERMINATOR_SIZE round_up(sizeof(struct _aarch64_ctx), 16)

#define EXTRA_CONTEXT_SIZE round_up(sizeof(struct extra_context), 16)


static void init_user_layout(struct rt_sigframe_user_layout *user) { const size_t reserved_size = sizeof(user->sigframe->uc.uc_mcontext.__reserved); memset(user, 0, sizeof(*user)); user->size = offsetof(struct rt_sigframe, uc.uc_mcontext.__reserved); user->limit = user->size + reserved_size; user->limit -= TERMINATOR_SIZE; user->limit -= EXTRA_CONTEXT_SIZE; /* Reserve space for extension and terminator ^ */ }

Contributors

PersonTokensPropCommitsCommitProp
Dave P Martin80100.00%2100.00%
Total80100.00%2100.00%


static size_t sigframe_size(struct rt_sigframe_user_layout const *user) { return round_up(max(user->size, sizeof(struct rt_sigframe)), 16); }

Contributors

PersonTokensPropCommitsCommitProp
Dave P Martin31100.00%1100.00%
Total31100.00%1100.00%

/* * Sanity limit on the approximate maximum size of signal frame we'll * try to generate. Stack alignment padding and the frame record are * not taken into account. This limit is not a guarantee and is * NOT ABI. */ #define SIGFRAME_MAXSZ SZ_64K
static int __sigframe_alloc(struct rt_sigframe_user_layout *user, unsigned long *offset, size_t size, bool extend) { size_t padded_size = round_up(size, 16); if (padded_size > user->limit - user->size && !user->extra_offset && extend) { int ret; user->limit += EXTRA_CONTEXT_SIZE; ret = __sigframe_alloc(user, &user->extra_offset, sizeof(struct extra_context), false); if (ret) { user->limit -= EXTRA_CONTEXT_SIZE; return ret; } /* Reserve space for the __reserved[] terminator */ user->size += TERMINATOR_SIZE; /* * Allow expansion up to SIGFRAME_MAXSZ, ensuring space for * the terminator: */ user->limit = SIGFRAME_MAXSZ - TERMINATOR_SIZE; } /* Still not enough space? Bad luck! */ if (padded_size > user->limit - user->size) return -ENOMEM; *offset = user->size; user->size += padded_size; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Dave P Martin146100.00%2100.00%
Total146100.00%2100.00%

/* * Allocate space for an optional record of <size> bytes in the user * signal frame. The offset from the signal frame base address to the * allocated block is assigned to *offset. */
static int sigframe_alloc(struct rt_sigframe_user_layout *user, unsigned long *offset, size_t size) { return __sigframe_alloc(user, offset, size, true); }

Contributors

PersonTokensPropCommitsCommitProp
Dave P Martin31100.00%1100.00%
Total31100.00%1100.00%

/* Allocate the null terminator record and prevent further allocations */
static int sigframe_alloc_end(struct rt_sigframe_user_layout *user) { int ret; /* Un-reserve the space reserved for the terminator: */ user->limit += TERMINATOR_SIZE; ret = sigframe_alloc(user, &user->end_offset, sizeof(struct _aarch64_ctx)); if (ret) return ret; /* Prevent further allocation: */ user->limit = user->size; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Dave P Martin58100.00%1100.00%
Total58100.00%1100.00%


static void __user *apply_user_offset( struct rt_sigframe_user_layout const *user, unsigned long offset) { char __user *base = (char __user *)user->sigframe; return base + offset; }

Contributors

PersonTokensPropCommitsCommitProp
Dave P Martin37100.00%1100.00%
Total37100.00%1100.00%


static int preserve_fpsimd_context(struct fpsimd_context __user *ctx) { struct fpsimd_state *fpsimd = &current->thread.fpsimd_state; int err; /* dump the hardware registers to the fpsimd_state structure */ fpsimd_preserve_current_state(); /* copy the FP and status/control registers */ err = __copy_to_user(ctx->vregs, fpsimd->vregs, sizeof(fpsimd->vregs)); __put_user_error(fpsimd->fpsr, &ctx->fpsr, err); __put_user_error(fpsimd->fpcr, &ctx->fpcr, err); /* copy the magic/size information */ __put_user_error(FPSIMD_MAGIC, &ctx->head.magic, err); __put_user_error(sizeof(struct fpsimd_context), &ctx->head.size, err); return err ? -EFAULT : 0; }

Contributors

PersonTokensPropCommitsCommitProp
Catalin Marinas11998.35%150.00%
Ard Biesheuvel21.65%150.00%
Total121100.00%2100.00%


static int restore_fpsimd_context(struct fpsimd_context __user *ctx) { struct fpsimd_state fpsimd; __u32 magic, size; int err = 0; /* check the magic/size information */ __get_user_error(magic, &ctx->head.magic, err); __get_user_error(size, &ctx->head.size, err); if (err) return -EFAULT; if (magic != FPSIMD_MAGIC || size != sizeof(struct fpsimd_context)) return -EINVAL; /* copy the FP and status/control registers */ err = __copy_from_user(fpsimd.vregs, ctx->vregs, sizeof(fpsimd.vregs)); __get_user_error(fpsimd.fpsr, &ctx->fpsr, err); __get_user_error(fpsimd.fpcr, &ctx->fpcr, err); /* load the hardware registers from the fpsimd_state structure */ if (!err) fpsimd_update_current_state(&fpsimd); return err ? -EFAULT : 0; }

Contributors

PersonTokensPropCommitsCommitProp
Catalin Marinas14999.33%150.00%
Ard Biesheuvel10.67%150.00%
Total150100.00%2100.00%

struct user_ctxs { struct fpsimd_context __user *fpsimd; };
static int parse_user_sigframe(struct user_ctxs *user, struct rt_sigframe __user *sf) { struct sigcontext __user *const sc = &sf->uc.uc_mcontext; struct _aarch64_ctx __user *head; char __user *base = (char __user *)&sc->__reserved; size_t offset = 0; size_t limit = sizeof(sc->__reserved); bool have_extra_context = false; char const __user *const sfp = (char const __user *)sf; user->fpsimd = NULL; if (!IS_ALIGNED((unsigned long)base, 16)) goto invalid; while (1) { int err = 0; u32 magic, size; char const __user *userp; struct extra_context const __user *extra; u64 extra_datap; u32 extra_size; struct _aarch64_ctx const __user *end; u32 end_magic, end_size; if (limit - offset < sizeof(*head)) goto invalid; if (!IS_ALIGNED(offset, 16)) goto invalid; head = (struct _aarch64_ctx __user *)(base + offset); __get_user_error(magic, &head->magic, err); __get_user_error(size, &head->size, err); if (err) return err; if (limit - offset < size) goto invalid; switch (magic) { case 0: if (size) goto invalid; goto done; case FPSIMD_MAGIC: if (user->fpsimd) goto invalid; if (size < sizeof(*user->fpsimd)) goto invalid; user->fpsimd = (struct fpsimd_context __user *)head; break; case ESR_MAGIC: /* ignore */ break; case EXTRA_MAGIC: if (have_extra_context) goto invalid; if (size < sizeof(*extra)) goto invalid; userp = (char const __user *)head; extra = (struct extra_context const __user *)userp; userp += size; __get_user_error(extra_datap, &extra->datap, err); __get_user_error(extra_size, &extra->size, err); if (err) return err; /* Check for the dummy terminator in __reserved[]: */ if (limit - offset - size < TERMINATOR_SIZE) goto invalid; end = (struct _aarch64_ctx const __user *)userp; userp += TERMINATOR_SIZE; __get_user_error(end_magic, &end->magic, err); __get_user_error(end_size, &end->size, err); if (err) return err; if (end_magic || end_size) goto invalid; /* Prevent looping/repeated parsing of extra_context */ have_extra_context = true; base = (__force void __user *)extra_datap; if (!IS_ALIGNED((unsigned long)base, 16)) goto invalid; if (!IS_ALIGNED(extra_size, 16)) goto invalid; if (base != userp) goto invalid; /* Reject "unreasonably large" frames: */ if (extra_size > sfp + SIGFRAME_MAXSZ - userp) goto invalid; /* * Ignore trailing terminator in __reserved[] * and start parsing extra data: */ offset = 0; limit = extra_size; continue; default: goto invalid; } if (size < sizeof(*head)) goto invalid; if (limit - offset < size) goto invalid; offset += size; } done: if (!user->fpsimd) goto invalid; return 0; invalid: return -EINVAL; }

Contributors

PersonTokensPropCommitsCommitProp
Dave P Martin584100.00%3100.00%
Total584100.00%3100.00%


static int restore_sigframe(struct pt_regs *regs, struct rt_sigframe __user *sf) { sigset_t set; int i, err; struct user_ctxs user; err = __copy_from_user(&set, &sf->uc.uc_sigmask, sizeof(set)); if (err == 0) set_current_blocked(&set); for (i = 0; i < 31; i++) __get_user_error(regs->regs[i], &sf->uc.uc_mcontext.regs[i], err); __get_user_error(regs->sp, &sf->uc.uc_mcontext.sp, err); __get_user_error(regs->pc, &sf->uc.uc_mcontext.pc, err); __get_user_error(regs->pstate, &sf->uc.uc_mcontext.pstate, err); /* * Avoid sys_rt_sigreturn() restarting. */ forget_syscall(regs); err |= !valid_user_regs(&regs->user_regs, current); if (err == 0) err = parse_user_sigframe(&user, sf); if (err == 0) err = restore_fpsimd_context(user.fpsimd); return err; }

Contributors

PersonTokensPropCommitsCommitProp
Catalin Marinas18087.80%240.00%
Dave P Martin2311.22%240.00%
Mark Rutland20.98%120.00%
Total205100.00%5100.00%


asmlinkage long sys_rt_sigreturn(struct pt_regs *regs) { struct rt_sigframe __user *frame; /* Always make any pending restarted system calls return -EINTR */ current->restart_block.fn = do_no_restart_syscall; /* * Since we stacked the signal on a 128-bit boundary, then 'sp' should * be word aligned here. */ if (regs->sp & 15) goto badframe; frame = (struct rt_sigframe __user *)regs->sp; if (!access_ok(VERIFY_READ, frame, sizeof (*frame))) goto badframe; if (restore_sigframe(regs, frame)) goto badframe; if (restore_altstack(&frame->uc.uc_stack)) goto badframe; return regs->regs[0]; badframe: if (show_unhandled_signals) pr_info_ratelimited("%s[%d]: bad frame in %s: pc=%08llx sp=%08llx\n", current->comm, task_pid_nr(current), __func__, regs->pc, regs->sp); force_sig(SIGSEGV, current); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Catalin Marinas14298.61%133.33%
Al Viro10.69%133.33%
Andrew Lutomirski10.69%133.33%
Total144100.00%3100.00%

/* Determine the layout of optional records in the signal frame */
static int setup_sigframe_layout(struct rt_sigframe_user_layout *user) { int err; err = sigframe_alloc(user, &user->fpsimd_offset, sizeof(struct fpsimd_context)); if (err) return err; /* fault information, if valid */ if (current->thread.fault_code) { err = sigframe_alloc(user, &user->esr_offset, sizeof(struct esr_context)); if (err) return err; } return sigframe_alloc_end(user); }

Contributors

PersonTokensPropCommitsCommitProp
Dave P Martin81100.00%3100.00%
Total81100.00%3100.00%


static int setup_sigframe(struct rt_sigframe_user_layout *user, struct pt_regs *regs, sigset_t *set) { int i, err = 0; struct rt_sigframe __user *sf = user->sigframe; /* set up the stack frame for unwinding */ __put_user_error(regs->regs[29], &user->next_frame->fp, err); __put_user_error(regs->regs[30], &user->next_frame->lr, err); for (i = 0; i < 31; i++) __put_user_error(regs->regs[i], &sf->uc.uc_mcontext.regs[i], err); __put_user_error(regs->sp, &sf->uc.uc_mcontext.sp, err); __put_user_error(regs->pc, &sf->uc.uc_mcontext.pc, err); __put_user_error(regs->pstate, &sf->uc.uc_mcontext.pstate, err); __put_user_error(current->thread.fault_address, &sf->uc.uc_mcontext.fault_address, err); err |= __copy_to_user(&sf->uc.uc_sigmask, set, sizeof(*set)); if (err == 0) { struct fpsimd_context __user *fpsimd_ctx = apply_user_offset(user, user->fpsimd_offset); err |= preserve_fpsimd_context(fpsimd_ctx); } /* fault information, if valid */ if (err == 0 && user->esr_offset) { struct esr_context __user *esr_ctx = apply_user_offset(user, user->esr_offset); __put_user_error(ESR_MAGIC, &esr_ctx->head.magic, err); __put_user_error(sizeof(*esr_ctx), &esr_ctx->head.size, err); __put_user_error(current->thread.fault_code, &esr_ctx->esr, err); } if (err == 0 && user->extra_offset) { char __user *sfp = (char __user *)user->sigframe; char __user *userp = apply_user_offset(user, user->extra_offset); struct extra_context __user *extra; struct _aarch64_ctx __user *end; u64 extra_datap; u32 extra_size; extra = (struct extra_context __user *)userp; userp += EXTRA_CONTEXT_SIZE; end = (struct _aarch64_ctx __user *)userp; userp += TERMINATOR_SIZE; /* * extra_datap is just written to the signal frame. * The value gets cast back to a void __user * * during sigreturn. */ extra_datap = (__force u64)userp; extra_size = sfp + round_up(user->size, 16) - userp; __put_user_error(EXTRA_MAGIC, &extra->head.magic, err); __put_user_error(EXTRA_CONTEXT_SIZE, &extra->head.size, err); __put_user_error(extra_datap, &extra->datap, err); __put_user_error(extra_size, &extra->size, err); /* Add the terminator */ __put_user_error(0, &end->magic, err); __put_user_error(0, &end->size, err); } /* set the "end" magic */ if (err == 0) { struct _aarch64_ctx __user *end = apply_user_offset(user, user->end_offset); __put_user_error(0, &end->magic, err); __put_user_error(0, &end->size, err); } return err; }

Contributors

PersonTokensPropCommitsCommitProp
Catalin Marinas27349.55%342.86%
Dave P Martin24544.46%342.86%
Will Deacon335.99%114.29%
Total551100.00%7100.00%


static int get_sigframe(struct rt_sigframe_user_layout *user, struct ksignal *ksig, struct pt_regs *regs) { unsigned long sp, sp_top; int err; init_user_layout(user); err = setup_sigframe_layout(user); if (err) return err; sp = sp_top = sigsp(regs->sp, ksig); sp = round_down(sp - sizeof(struct frame_record), 16); user->next_frame = (struct frame_record __user *)sp; sp = round_down(sp, 16) - sigframe_size(user); user->sigframe = (struct rt_sigframe __user *)sp; /* * Check that we can actually write to the signal frame. */ if (!access_ok(VERIFY_WRITE, user->sigframe, sp_top - sp)) return -EFAULT; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Dave P Martin7553.96%240.00%
Catalin Marinas5640.29%120.00%
Richard Weinberger64.32%120.00%
Will Deacon21.44%120.00%
Total139100.00%5100.00%


static void setup_return(struct pt_regs *regs, struct k_sigaction *ka, struct rt_sigframe_user_layout *user, int usig) { __sigrestore_t sigtramp; regs->regs[0] = usig; regs->sp = (unsigned long)user->sigframe; regs->regs[29] = (unsigned long)&user->next_frame->fp; regs->pc = (unsigned long)ka->sa.sa_handler; if (ka->sa.sa_flags & SA_RESTORER) sigtramp = ka->sa.sa_restorer; else sigtramp = VDSO_SYMBOL(current->mm->context.vdso, sigtramp); regs->regs[30] = (unsigned long)sigtramp; }

Contributors

PersonTokensPropCommitsCommitProp
Catalin Marinas10481.89%133.33%
Dave P Martin1411.02%133.33%
Will Deacon97.09%133.33%
Total127100.00%3100.00%


static int setup_rt_frame(int usig, struct ksignal *ksig, sigset_t *set, struct pt_regs *regs) { struct rt_sigframe_user_layout user; struct rt_sigframe __user *frame; int err = 0; if (get_sigframe(&user, ksig, regs)) return 1; frame = user.sigframe; __put_user_error(0, &frame->uc.uc_flags, err); __put_user_error(NULL, &frame->uc.uc_link, err); err |= __save_altstack(&frame->uc.uc_stack, regs->sp); err |= setup_sigframe(&user, regs, set); if (err == 0) { setup_return(regs, &ksig->ka, &user, usig); if (ksig->ka.sa.sa_flags & SA_SIGINFO) { err |= copy_siginfo_to_user(&frame->info, &ksig->info); regs->regs[1] = (unsigned long)&frame->info; regs->regs[2] = (unsigned long)&frame->uc; } } return err; }

Contributors

PersonTokensPropCommitsCommitProp
Catalin Marinas16581.68%120.00%
Dave P Martin199.41%120.00%
Richard Weinberger125.94%120.00%
Al Viro41.98%120.00%
Will Deacon20.99%120.00%
Total202100.00%5100.00%


static void setup_restart_syscall(struct pt_regs *regs) { if (is_compat_task()) compat_setup_restart_syscall(regs); else regs->regs[8] = __NR_restart_syscall; }

Contributors

PersonTokensPropCommitsCommitProp
Catalin Marinas31100.00%1100.00%
Total31100.00%1100.00%

/* * OK, we're invoking a handler */
static void handle_signal(struct ksignal *ksig, struct pt_regs *regs) { struct task_struct *tsk = current; sigset_t *oldset = sigmask_to_save(); int usig = ksig->sig; int ret; /* * Set up the stack frame */ if (is_compat_task()) { if (ksig->ka.sa.sa_flags & SA_SIGINFO) ret = compat_setup_rt_frame(usig, ksig, oldset, regs); else ret = compat_setup_frame(usig, ksig, oldset, regs); } else { ret = setup_rt_frame(usig, ksig, oldset, regs); } /* * Check that the resulting registers are actually sane. */ ret |= !valid_user_regs(&regs->user_regs, current); /* * Fast forward the stepping logic so we step into the signal * handler. */ if (!ret) user_fastforward_single_step(tsk); signal_setup_done(ret, ksig, 0); }

Contributors

PersonTokensPropCommitsCommitProp
Catalin Marinas12087.59%133.33%
Richard Weinberger1510.95%133.33%
Mark Rutland21.46%133.33%
Total137100.00%3100.00%

/* * Note that 'init' is a special process: it doesn't get signals it doesn't * want to handle. Thus you cannot kill init even with a SIGKILL even by * mistake. * * Note that we go through the signals twice: once to check the signals that * the kernel can handle, and then we build all the user-level signal handling * stack-frames in one go after that. */
static void do_signal(struct pt_regs *regs) { unsigned long continue_addr = 0, restart_addr = 0; int retval = 0; struct ksignal ksig; /* * If we were from a system call, check for system call restarting... */ if (in_syscall(regs)) { continue_addr = regs->pc; restart_addr = continue_addr - (compat_thumb_mode(regs) ? 2 : 4); retval = regs->regs[0]; /* * Avoid additional syscall restarting via ret_to_user. */ forget_syscall(regs); /* * Prepare for system call restart. We do this here so that a * debugger will see the already changed PC. */ switch (retval) { case -ERESTARTNOHAND: case -ERESTARTSYS: case -ERESTARTNOINTR: case -ERESTART_RESTARTBLOCK: regs->regs[0] = regs->orig_x0; regs->pc = restart_addr; break; } } /* * Get the signal to deliver. When running under ptrace, at this point * the debugger may change all of our registers. */ if (get_signal(&ksig)) { /* * Depending on the signal settings, we may need to revert the * decision to restart the system call, but skip this if a * debugger has chosen to restart at a different PC. */ if (regs->pc == restart_addr && (retval == -ERESTARTNOHAND || retval == -ERESTART_RESTARTBLOCK || (retval == -ERESTARTSYS && !(ksig.ka.sa.sa_flags & SA_RESTART)))) { regs->regs[0] = -EINTR; regs->pc = continue_addr; } handle_signal(&ksig, regs); return; } /* * Handle restarting a different system call. As above, if a debugger * has chosen to restart at a different PC, ignore the restart. */ if (in_syscall(regs) && regs->pc == restart_addr) { if (retval == -ERESTART_RESTARTBLOCK) setup_restart_syscall(regs); user_rewind_single_step(current); } restore_saved_sigmask(); }

Contributors

PersonTokensPropCommitsCommitProp
Catalin Marinas21090.52%133.33%
Richard Weinberger114.74%133.33%
Dave P Martin114.74%133.33%
Total232100.00%3100.00%


asmlinkage void do_notify_resume(struct pt_regs *regs, unsigned int thread_flags) { /* * The assembly code enters us with IRQs off, but it hasn't * informed the tracing code of that for efficiency reasons. * Update the trace code with the current status. */ trace_hardirqs_off(); do { /* Check valid user FS if needed */ addr_limit_user_check(); if (thread_flags & _TIF_NEED_RESCHED) { schedule(); } else { local_irq_enable(); if (thread_flags & _TIF_UPROBE) uprobe_notify_resume(regs); if (thread_flags & _TIF_SIGPENDING) do_signal(regs); if (thread_flags & _TIF_NOTIFY_RESUME) { clear_thread_flag(TIF_NOTIFY_RESUME); tracehook_notify_resume(regs); } if (thread_flags & _TIF_FOREIGN_FPSTATE) fpsimd_restore_current_state(); } local_irq_disable(); thread_flags = READ_ONCE(current_thread_info()->flags); } while (thread_flags & _TIF_WORK_MASK); }

Contributors

PersonTokensPropCommitsCommitProp
Catalin Marinas4439.29%116.67%
Chris Metcalf4237.50%116.67%
Pratyush Anand119.82%116.67%
Ard Biesheuvel98.04%116.67%
Thomas Garnier65.36%233.33%
Total112100.00%6100.00%


Overall Contributors

PersonTokensPropCommitsCommitProp
Catalin Marinas165549.15%313.04%
Dave P Martin152945.41%626.09%
Will Deacon521.54%28.70%
Richard Weinberger441.31%28.70%
Chris Metcalf421.25%14.35%
Ard Biesheuvel120.36%28.70%
Pratyush Anand110.33%14.35%
Thomas Garnier90.27%28.70%
Al Viro50.15%14.35%
Mark Rutland40.12%14.35%
AKASHI Takahiro30.09%14.35%
Andrew Lutomirski10.03%14.35%
Total3367100.00%23100.00%
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with cregit.