Skip to content

Commit

Permalink
bpf: Add file_pos field to bpf_sysctl ctx
Browse files Browse the repository at this point in the history
Add file_pos field to bpf_sysctl context to read and write sysctl file
position at which sysctl is being accessed (read or written).

The field can be used to e.g. override whole sysctl value on write to
sysctl even when sys_write is called by user space with file_pos > 0. Or
BPF program may reject such accesses.

Signed-off-by: Andrey Ignatov <rdna@fb.com>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
  • Loading branch information
Andrey Ignatov authored and Alexei Starovoitov committed Apr 12, 2019
1 parent 4e63acd commit e1550bf
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 8 deletions.
2 changes: 1 addition & 1 deletion fs/proc/proc_sysctl.c
Original file line number Diff line number Diff line change
Expand Up @@ -590,7 +590,7 @@ static ssize_t proc_sys_call_handler(struct file *filp, void __user *buf,
goto out;

error = BPF_CGROUP_RUN_PROG_SYSCTL(head, table, write, buf, &count,
&new_buf);
ppos, &new_buf);
if (error)
goto out;

Expand Down
9 changes: 5 additions & 4 deletions include/linux/bpf-cgroup.h
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@ int __cgroup_bpf_check_dev_permission(short dev_type, u32 major, u32 minor,
int __cgroup_bpf_run_filter_sysctl(struct ctl_table_header *head,
struct ctl_table *table, int write,
void __user *buf, size_t *pcount,
void **new_buf, enum bpf_attach_type type);
loff_t *ppos, void **new_buf,
enum bpf_attach_type type);

static inline enum bpf_cgroup_storage_type cgroup_storage_type(
struct bpf_map *map)
Expand Down Expand Up @@ -262,12 +263,12 @@ int bpf_percpu_cgroup_storage_update(struct bpf_map *map, void *key,
})


#define BPF_CGROUP_RUN_PROG_SYSCTL(head, table, write, buf, count, nbuf) \
#define BPF_CGROUP_RUN_PROG_SYSCTL(head, table, write, buf, count, pos, nbuf) \
({ \
int __ret = 0; \
if (cgroup_bpf_enabled) \
__ret = __cgroup_bpf_run_filter_sysctl(head, table, write, \
buf, count, nbuf, \
buf, count, pos, nbuf, \
BPF_CGROUP_SYSCTL); \
__ret; \
})
Expand Down Expand Up @@ -340,7 +341,7 @@ static inline int bpf_percpu_cgroup_storage_update(struct bpf_map *map,
#define BPF_CGROUP_RUN_PROG_UDP6_SENDMSG_LOCK(sk, uaddr, t_ctx) ({ 0; })
#define BPF_CGROUP_RUN_PROG_SOCK_OPS(sock_ops) ({ 0; })
#define BPF_CGROUP_RUN_PROG_DEVICE_CGROUP(type,major,minor,access) ({ 0; })
#define BPF_CGROUP_RUN_PROG_SYSCTL(head,table,write,buf,count,nbuf) ({ 0; })
#define BPF_CGROUP_RUN_PROG_SYSCTL(head,table,write,buf,count,pos,nbuf) ({ 0; })

#define for_each_cgroup_storage_type(stype) for (; false; )

Expand Down
3 changes: 3 additions & 0 deletions include/linux/filter.h
Original file line number Diff line number Diff line change
Expand Up @@ -1188,6 +1188,9 @@ struct bpf_sysctl_kern {
size_t new_len;
int new_updated;
int write;
loff_t *ppos;
/* Temporary "register" for indirect stores to ppos. */
u64 tmp_reg;
};

#endif /* __LINUX_FILTER_H__ */
3 changes: 3 additions & 0 deletions include/uapi/linux/bpf.h
Original file line number Diff line number Diff line change
Expand Up @@ -3391,6 +3391,9 @@ struct bpf_sysctl {
__u32 write; /* Sysctl is being read (= 0) or written (= 1).
* Allows 1,2,4-byte read, but no write.
*/
__u32 file_pos; /* Sysctl file position to read from, write to.
* Allows 1,2,4-byte read an 4-byte write.
*/
};

#endif /* _UAPI__LINUX_BPF_H__ */
54 changes: 51 additions & 3 deletions kernel/bpf/cgroup.c
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,9 @@ const struct bpf_verifier_ops cg_dev_verifier_ops = {
* @pcount: value-result argument: value is size of buffer pointed to by @buf,
* result is size of @new_buf if program set new value, initial value
* otherwise
* @ppos: value-result argument: value is position at which read from or write
* to sysctl is happening, result is new position if program overrode it,
* initial value otherwise
* @new_buf: pointer to pointer to new buffer that will be allocated if program
* overrides new value provided by user space on sysctl write
* NOTE: it's caller responsibility to free *new_buf if it was set
Expand All @@ -796,12 +799,14 @@ const struct bpf_verifier_ops cg_dev_verifier_ops = {
int __cgroup_bpf_run_filter_sysctl(struct ctl_table_header *head,
struct ctl_table *table, int write,
void __user *buf, size_t *pcount,
void **new_buf, enum bpf_attach_type type)
loff_t *ppos, void **new_buf,
enum bpf_attach_type type)
{
struct bpf_sysctl_kern ctx = {
.head = head,
.table = table,
.write = write,
.ppos = ppos,
.cur_val = NULL,
.cur_len = PAGE_SIZE,
.new_val = NULL,
Expand Down Expand Up @@ -1030,14 +1035,22 @@ static bool sysctl_is_valid_access(int off, int size, enum bpf_access_type type,
{
const int size_default = sizeof(__u32);

if (off < 0 || off + size > sizeof(struct bpf_sysctl) ||
off % size || type != BPF_READ)
if (off < 0 || off + size > sizeof(struct bpf_sysctl) || off % size)
return false;

switch (off) {
case offsetof(struct bpf_sysctl, write):
if (type != BPF_READ)
return false;
bpf_ctx_record_field_size(info, size_default);
return bpf_ctx_narrow_access_ok(off, size, size_default);
case offsetof(struct bpf_sysctl, file_pos):
if (type == BPF_READ) {
bpf_ctx_record_field_size(info, size_default);
return bpf_ctx_narrow_access_ok(off, size, size_default);
} else {
return size == size_default;
}
default:
return false;
}
Expand All @@ -1059,6 +1072,41 @@ static u32 sysctl_convert_ctx_access(enum bpf_access_type type,
write),
target_size));
break;
case offsetof(struct bpf_sysctl, file_pos):
/* ppos is a pointer so it should be accessed via indirect
* loads and stores. Also for stores additional temporary
* register is used since neither src_reg nor dst_reg can be
* overridden.
*/
if (type == BPF_WRITE) {
int treg = BPF_REG_9;

if (si->src_reg == treg || si->dst_reg == treg)
--treg;
if (si->src_reg == treg || si->dst_reg == treg)
--treg;
*insn++ = BPF_STX_MEM(
BPF_DW, si->dst_reg, treg,
offsetof(struct bpf_sysctl_kern, tmp_reg));
*insn++ = BPF_LDX_MEM(
BPF_FIELD_SIZEOF(struct bpf_sysctl_kern, ppos),
treg, si->dst_reg,
offsetof(struct bpf_sysctl_kern, ppos));
*insn++ = BPF_STX_MEM(
BPF_SIZEOF(u32), treg, si->src_reg, 0);
*insn++ = BPF_LDX_MEM(
BPF_DW, treg, si->dst_reg,
offsetof(struct bpf_sysctl_kern, tmp_reg));
} else {
*insn++ = BPF_LDX_MEM(
BPF_FIELD_SIZEOF(struct bpf_sysctl_kern, ppos),
si->dst_reg, si->src_reg,
offsetof(struct bpf_sysctl_kern, ppos));
*insn++ = BPF_LDX_MEM(
BPF_SIZE(si->code), si->dst_reg, si->dst_reg, 0);
}
*target_size = sizeof(u32);
break;
}

return insn - insn_buf;
Expand Down

0 comments on commit e1550bf

Please sign in to comment.