diff options
Diffstat (limited to 'kernel/bpf/cgroup.c')
-rw-r--r-- | kernel/bpf/cgroup.c | 54 |
1 files changed, 51 insertions, 3 deletions
diff --git a/kernel/bpf/cgroup.c b/kernel/bpf/cgroup.c index ba4e21986760..b2adf22139b3 100644 --- a/kernel/bpf/cgroup.c +++ b/kernel/bpf/cgroup.c | |||
@@ -782,6 +782,9 @@ const struct bpf_verifier_ops cg_dev_verifier_ops = { | |||
782 | * @pcount: value-result argument: value is size of buffer pointed to by @buf, | 782 | * @pcount: value-result argument: value is size of buffer pointed to by @buf, |
783 | * result is size of @new_buf if program set new value, initial value | 783 | * result is size of @new_buf if program set new value, initial value |
784 | * otherwise | 784 | * otherwise |
785 | * @ppos: value-result argument: value is position at which read from or write | ||
786 | * to sysctl is happening, result is new position if program overrode it, | ||
787 | * initial value otherwise | ||
785 | * @new_buf: pointer to pointer to new buffer that will be allocated if program | 788 | * @new_buf: pointer to pointer to new buffer that will be allocated if program |
786 | * overrides new value provided by user space on sysctl write | 789 | * overrides new value provided by user space on sysctl write |
787 | * NOTE: it's caller responsibility to free *new_buf if it was set | 790 | * NOTE: it's caller responsibility to free *new_buf if it was set |
@@ -796,12 +799,14 @@ const struct bpf_verifier_ops cg_dev_verifier_ops = { | |||
796 | int __cgroup_bpf_run_filter_sysctl(struct ctl_table_header *head, | 799 | int __cgroup_bpf_run_filter_sysctl(struct ctl_table_header *head, |
797 | struct ctl_table *table, int write, | 800 | struct ctl_table *table, int write, |
798 | void __user *buf, size_t *pcount, | 801 | void __user *buf, size_t *pcount, |
799 | void **new_buf, enum bpf_attach_type type) | 802 | loff_t *ppos, void **new_buf, |
803 | enum bpf_attach_type type) | ||
800 | { | 804 | { |
801 | struct bpf_sysctl_kern ctx = { | 805 | struct bpf_sysctl_kern ctx = { |
802 | .head = head, | 806 | .head = head, |
803 | .table = table, | 807 | .table = table, |
804 | .write = write, | 808 | .write = write, |
809 | .ppos = ppos, | ||
805 | .cur_val = NULL, | 810 | .cur_val = NULL, |
806 | .cur_len = PAGE_SIZE, | 811 | .cur_len = PAGE_SIZE, |
807 | .new_val = NULL, | 812 | .new_val = NULL, |
@@ -1030,14 +1035,22 @@ static bool sysctl_is_valid_access(int off, int size, enum bpf_access_type type, | |||
1030 | { | 1035 | { |
1031 | const int size_default = sizeof(__u32); | 1036 | const int size_default = sizeof(__u32); |
1032 | 1037 | ||
1033 | if (off < 0 || off + size > sizeof(struct bpf_sysctl) || | 1038 | if (off < 0 || off + size > sizeof(struct bpf_sysctl) || off % size) |
1034 | off % size || type != BPF_READ) | ||
1035 | return false; | 1039 | return false; |
1036 | 1040 | ||
1037 | switch (off) { | 1041 | switch (off) { |
1038 | case offsetof(struct bpf_sysctl, write): | 1042 | case offsetof(struct bpf_sysctl, write): |
1043 | if (type != BPF_READ) | ||
1044 | return false; | ||
1039 | bpf_ctx_record_field_size(info, size_default); | 1045 | bpf_ctx_record_field_size(info, size_default); |
1040 | return bpf_ctx_narrow_access_ok(off, size, size_default); | 1046 | return bpf_ctx_narrow_access_ok(off, size, size_default); |
1047 | case offsetof(struct bpf_sysctl, file_pos): | ||
1048 | if (type == BPF_READ) { | ||
1049 | bpf_ctx_record_field_size(info, size_default); | ||
1050 | return bpf_ctx_narrow_access_ok(off, size, size_default); | ||
1051 | } else { | ||
1052 | return size == size_default; | ||
1053 | } | ||
1041 | default: | 1054 | default: |
1042 | return false; | 1055 | return false; |
1043 | } | 1056 | } |
@@ -1059,6 +1072,41 @@ static u32 sysctl_convert_ctx_access(enum bpf_access_type type, | |||
1059 | write), | 1072 | write), |
1060 | target_size)); | 1073 | target_size)); |
1061 | break; | 1074 | break; |
1075 | case offsetof(struct bpf_sysctl, file_pos): | ||
1076 | /* ppos is a pointer so it should be accessed via indirect | ||
1077 | * loads and stores. Also for stores additional temporary | ||
1078 | * register is used since neither src_reg nor dst_reg can be | ||
1079 | * overridden. | ||
1080 | */ | ||
1081 | if (type == BPF_WRITE) { | ||
1082 | int treg = BPF_REG_9; | ||
1083 | |||
1084 | if (si->src_reg == treg || si->dst_reg == treg) | ||
1085 | --treg; | ||
1086 | if (si->src_reg == treg || si->dst_reg == treg) | ||
1087 | --treg; | ||
1088 | *insn++ = BPF_STX_MEM( | ||
1089 | BPF_DW, si->dst_reg, treg, | ||
1090 | offsetof(struct bpf_sysctl_kern, tmp_reg)); | ||
1091 | *insn++ = BPF_LDX_MEM( | ||
1092 | BPF_FIELD_SIZEOF(struct bpf_sysctl_kern, ppos), | ||
1093 | treg, si->dst_reg, | ||
1094 | offsetof(struct bpf_sysctl_kern, ppos)); | ||
1095 | *insn++ = BPF_STX_MEM( | ||
1096 | BPF_SIZEOF(u32), treg, si->src_reg, 0); | ||
1097 | *insn++ = BPF_LDX_MEM( | ||
1098 | BPF_DW, treg, si->dst_reg, | ||
1099 | offsetof(struct bpf_sysctl_kern, tmp_reg)); | ||
1100 | } else { | ||
1101 | *insn++ = BPF_LDX_MEM( | ||
1102 | BPF_FIELD_SIZEOF(struct bpf_sysctl_kern, ppos), | ||
1103 | si->dst_reg, si->src_reg, | ||
1104 | offsetof(struct bpf_sysctl_kern, ppos)); | ||
1105 | *insn++ = BPF_LDX_MEM( | ||
1106 | BPF_SIZE(si->code), si->dst_reg, si->dst_reg, 0); | ||
1107 | } | ||
1108 | *target_size = sizeof(u32); | ||
1109 | break; | ||
1062 | } | 1110 | } |
1063 | 1111 | ||
1064 | return insn - insn_buf; | 1112 | return insn - insn_buf; |