diff options
author | Peter Zijlstra <a.p.zijlstra@chello.nl> | 2009-06-12 06:46:55 -0400 |
---|---|---|
committer | Ingo Molnar <mingo@elte.hu> | 2009-06-12 08:28:52 -0400 |
commit | 974802eaa1afdc87e00821df7020a2b3c6fee623 (patch) | |
tree | b5c38bece7f4948fe9d5cf9be02a0042e0fa39f6 /kernel | |
parent | bbd36e5e6aa6f1757c84cdb406b6eb81686d14af (diff) |
perf_counter: Add forward/backward attribute ABI compatibility
Provide for means of extending the perf_counter_attr in a 'natural' way.
We allow growing the structure by appending fields at the end by specifying
the full structure size inside it.
When a new kernel sees a smaller (old) structure, it will 0 pad the tail.
When an old kernel sees a larger (new) structure, it will verify the tail
consists of 0s, otherwise fail.
If we fail due to a size-mismatch, we return -E2BIG and write the kernel's
native attribe size back into the provided structure.
Furthermore, add some attribute verification, so that we'll fail counter
creation when unknown bits are present (PERF_SAMPLE, PERF_FORMAT, or in
the __reserved fields).
(This ABI detail is introduced while keeping the existing syscall ABI.)
Signed-off-by: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Mike Galbraith <efault@gmx.de>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Arnaldo Carvalho de Melo <acme@redhat.com>
LKML-Reference: <new-submission>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
Diffstat (limited to 'kernel')
-rw-r--r-- | kernel/perf_counter.c | 89 |
1 files changed, 86 insertions, 3 deletions
diff --git a/kernel/perf_counter.c b/kernel/perf_counter.c index 663bbe015057..29b685f551aa 100644 --- a/kernel/perf_counter.c +++ b/kernel/perf_counter.c | |||
@@ -3584,6 +3584,9 @@ perf_counter_alloc(struct perf_counter_attr *attr, | |||
3584 | case PERF_TYPE_TRACEPOINT: | 3584 | case PERF_TYPE_TRACEPOINT: |
3585 | pmu = tp_perf_counter_init(counter); | 3585 | pmu = tp_perf_counter_init(counter); |
3586 | break; | 3586 | break; |
3587 | |||
3588 | default: | ||
3589 | break; | ||
3587 | } | 3590 | } |
3588 | done: | 3591 | done: |
3589 | err = 0; | 3592 | err = 0; |
@@ -3610,6 +3613,85 @@ done: | |||
3610 | return counter; | 3613 | return counter; |
3611 | } | 3614 | } |
3612 | 3615 | ||
3616 | static int perf_copy_attr(struct perf_counter_attr __user *uattr, | ||
3617 | struct perf_counter_attr *attr) | ||
3618 | { | ||
3619 | int ret; | ||
3620 | u32 size; | ||
3621 | |||
3622 | if (!access_ok(VERIFY_WRITE, uattr, PERF_ATTR_SIZE_VER0)) | ||
3623 | return -EFAULT; | ||
3624 | |||
3625 | /* | ||
3626 | * zero the full structure, so that a short copy will be nice. | ||
3627 | */ | ||
3628 | memset(attr, 0, sizeof(*attr)); | ||
3629 | |||
3630 | ret = get_user(size, &uattr->size); | ||
3631 | if (ret) | ||
3632 | return ret; | ||
3633 | |||
3634 | if (size > PAGE_SIZE) /* silly large */ | ||
3635 | goto err_size; | ||
3636 | |||
3637 | if (!size) /* abi compat */ | ||
3638 | size = PERF_ATTR_SIZE_VER0; | ||
3639 | |||
3640 | if (size < PERF_ATTR_SIZE_VER0) | ||
3641 | goto err_size; | ||
3642 | |||
3643 | /* | ||
3644 | * If we're handed a bigger struct than we know of, | ||
3645 | * ensure all the unknown bits are 0. | ||
3646 | */ | ||
3647 | if (size > sizeof(*attr)) { | ||
3648 | unsigned long val; | ||
3649 | unsigned long __user *addr; | ||
3650 | unsigned long __user *end; | ||
3651 | |||
3652 | addr = PTR_ALIGN((void __user *)uattr + sizeof(*attr), | ||
3653 | sizeof(unsigned long)); | ||
3654 | end = PTR_ALIGN((void __user *)uattr + size, | ||
3655 | sizeof(unsigned long)); | ||
3656 | |||
3657 | for (; addr < end; addr += sizeof(unsigned long)) { | ||
3658 | ret = get_user(val, addr); | ||
3659 | if (ret) | ||
3660 | return ret; | ||
3661 | if (val) | ||
3662 | goto err_size; | ||
3663 | } | ||
3664 | } | ||
3665 | |||
3666 | ret = copy_from_user(attr, uattr, size); | ||
3667 | if (ret) | ||
3668 | return -EFAULT; | ||
3669 | |||
3670 | /* | ||
3671 | * If the type exists, the corresponding creation will verify | ||
3672 | * the attr->config. | ||
3673 | */ | ||
3674 | if (attr->type >= PERF_TYPE_MAX) | ||
3675 | return -EINVAL; | ||
3676 | |||
3677 | if (attr->__reserved_1 || attr->__reserved_2 || attr->__reserved_3) | ||
3678 | return -EINVAL; | ||
3679 | |||
3680 | if (attr->sample_type & ~(PERF_SAMPLE_MAX-1)) | ||
3681 | return -EINVAL; | ||
3682 | |||
3683 | if (attr->read_format & ~(PERF_FORMAT_MAX-1)) | ||
3684 | return -EINVAL; | ||
3685 | |||
3686 | out: | ||
3687 | return ret; | ||
3688 | |||
3689 | err_size: | ||
3690 | put_user(sizeof(*attr), &uattr->size); | ||
3691 | ret = -E2BIG; | ||
3692 | goto out; | ||
3693 | } | ||
3694 | |||
3613 | /** | 3695 | /** |
3614 | * sys_perf_counter_open - open a performance counter, associate it to a task/cpu | 3696 | * sys_perf_counter_open - open a performance counter, associate it to a task/cpu |
3615 | * | 3697 | * |
@@ -3619,7 +3701,7 @@ done: | |||
3619 | * @group_fd: group leader counter fd | 3701 | * @group_fd: group leader counter fd |
3620 | */ | 3702 | */ |
3621 | SYSCALL_DEFINE5(perf_counter_open, | 3703 | SYSCALL_DEFINE5(perf_counter_open, |
3622 | const struct perf_counter_attr __user *, attr_uptr, | 3704 | struct perf_counter_attr __user *, attr_uptr, |
3623 | pid_t, pid, int, cpu, int, group_fd, unsigned long, flags) | 3705 | pid_t, pid, int, cpu, int, group_fd, unsigned long, flags) |
3624 | { | 3706 | { |
3625 | struct perf_counter *counter, *group_leader; | 3707 | struct perf_counter *counter, *group_leader; |
@@ -3635,8 +3717,9 @@ SYSCALL_DEFINE5(perf_counter_open, | |||
3635 | if (flags) | 3717 | if (flags) |
3636 | return -EINVAL; | 3718 | return -EINVAL; |
3637 | 3719 | ||
3638 | if (copy_from_user(&attr, attr_uptr, sizeof(attr)) != 0) | 3720 | ret = perf_copy_attr(attr_uptr, &attr); |
3639 | return -EFAULT; | 3721 | if (ret) |
3722 | return ret; | ||
3640 | 3723 | ||
3641 | if (!attr.exclude_kernel) { | 3724 | if (!attr.exclude_kernel) { |
3642 | if (perf_paranoid_kernel() && !capable(CAP_SYS_ADMIN)) | 3725 | if (perf_paranoid_kernel() && !capable(CAP_SYS_ADMIN)) |