Skip to contentSkip to conversation

Add BPF_SYNCHRONIZE bpf(2) command

RFC Superseded Daniel Colascione 8 participants 29 messages Jul 7, 2018 – Jul 27, 2018

Subsystems (4)

v1 of 3 Superseded Latest is v3 →

lore
Share & tools
lore
#0 lore raw

RFC 1/1 Add BPF_SYNCHRONIZE bpf(2) command

BPF_SYNCHRONIZE waits for any BPF programs active at the time of BPF_SYNCHRONIZE to complete, allowing userspace to ensure atomicity of RCU data structure operations with respect to active programs. For example, userspace can update a map->map entry to point to a new map, use BPF_SYNCHRONIZE to wait for any BPF programs using the old map to complete, and then drain the old map without fear that BPF programs may still be updating it.

---
 include/uapi/linux/bpf.h |  1 +
 kernel/bpf/syscall.c     | 14 ++++++++++++++
 2 files changed, 15 insertions(+)
include/uapi/linux/bpf.h +1 -0
index b7db3261c62d..4365c50e8055 100644
@@ -98,6 +98,7 @@ enum bpf_cmd {
 	BPF_BTF_LOAD,
 	BPF_BTF_GET_FD_BY_ID,
 	BPF_TASK_FD_QUERY,
+	BPF_SYNCHRONIZE,
 };
 
 enum bpf_map_type {
kernel/bpf/syscall.c +14 -0
index d10ecd78105f..60ec7811846e 100644
@@ -2272,6 +2272,20 @@ SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *, uattr, unsigned int, siz
 	if (sysctl_unprivileged_bpf_disabled && !capable(CAP_SYS_ADMIN))
 		return -EPERM;
 
+	if (cmd == BPF_SYNCHRONIZE) {
+		if (uattr != NULL || size != 0)
+			return -EINVAL;
+		err = security_bpf(cmd, NULL, 0);
+		if (err < 0)
+			return err;
+		/* BPF programs are run with preempt disabled, so
+		 * synchronize_sched is sufficient even with
+		 * RCU_PREEMPT.
+		 */
+		synchronize_sched();
+		return 0;
+	}
+
 	err = bpf_check_uarg_tail_zero(uattr, sizeof(attr), size);
 	if (err)
 		return err;
Signature · 1 line
2.18.0.203.gfac676dfb9-goog
#1 lore raw
Quoted 36 lines · Daniel Colascione

BPF_SYNCHRONIZE waits for any BPF programs active at the time of BPF_SYNCHRONIZE to complete, allowing userspace to ensure atomicity of RCU data structure operations with respect to active programs. For example, userspace can update a map->map entry to point to a new map, use BPF_SYNCHRONIZE to wait for any BPF programs using the old map to complete, and then drain the old map without fear that BPF programs may still be updating it.

---
 include/uapi/linux/bpf.h |  1 +
 kernel/bpf/syscall.c     | 14 ++++++++++++++
 2 files changed, 15 insertions(+)
include/uapi/linux/bpf.h +1 -0
index b7db3261c62d..4365c50e8055 100644
@@ -98,6 +98,7 @@ enum bpf_cmd {
 	BPF_BTF_LOAD,
 	BPF_BTF_GET_FD_BY_ID,
 	BPF_TASK_FD_QUERY,
+	BPF_SYNCHRONIZE,
 };
 
 enum bpf_map_type {
kernel/bpf/syscall.c +2 -0
index d10ecd78105f..60ec7811846e 100644
@@ -2272,6 +2272,20 @@ SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *, uattr, unsigned int, siz
 	if (sysctl_unprivileged_bpf_disabled && !capable(CAP_SYS_ADMIN))
 		return -EPERM;
 
+	if (cmd == BPF_SYNCHRONIZE) {
+		if (uattr != NULL || size != 0)

kernel/bpf/syscall.c SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *, uattr, unsigned int, siz · line 2286

Quoted 10 lines
kernel/bpf/syscall.c +10 -0
+			return -EINVAL;
+		err = security_bpf(cmd, NULL, 0);
+		if (err < 0)
+			return err;
+		/* BPF programs are run with preempt disabled, so
+		 * synchronize_sched is sufficient even with
+		 * RCU_PREEMPT.
+		 */
+		synchronize_sched();
+		return 0;

I don't think it's necessary. sys_membarrier() can do this already and some folks use it exactly for this use case.

#2 lore raw
Quoted 35 lines · Alexei Starovoitov
Quoted 34 lines · Daniel Colascione

BPF_SYNCHRONIZE waits for any BPF programs active at the time of BPF_SYNCHRONIZE to complete, allowing userspace to ensure atomicity of RCU data structure operations with respect to active programs. For example, userspace can update a map->map entry to point to a new map, use BPF_SYNCHRONIZE to wait for any BPF programs using the old map to complete, and then drain the old map without fear that BPF programs may still be updating it.

---
 include/uapi/linux/bpf.h |  1 +
 kernel/bpf/syscall.c     | 14 ++++++++++++++
 2 files changed, 15 insertions(+)
include/uapi/linux/bpf.h +1 -0
index b7db3261c62d..4365c50e8055 100644
@@ -98,6 +98,7 @@ enum bpf_cmd {
      BPF_BTF_LOAD,
      BPF_BTF_GET_FD_BY_ID,
      BPF_TASK_FD_QUERY,
+     BPF_SYNCHRONIZE,
 };
 
 enum bpf_map_type {
kernel/bpf/syscall.c +0 -0
index d10ecd78105f..60ec7811846e 100644
@@ -2272,6 +2272,20 @@ SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *, uattr, unsigned int, siz
      if (sysctl_unprivileged_bpf_disabled && !capable(CAP_SYS_ADMIN))
              return -EPERM;
 
Quoted 15 lines

kernel/bpf/syscall.c SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *, uattr, unsigned int, siz · line 2286

Quoted 12 lines
kernel/bpf/syscall.c +12 -0
+     if (cmd == BPF_SYNCHRONIZE) {
+             if (uattr != NULL || size != 0)
+                     return -EINVAL;
+             err = security_bpf(cmd, NULL, 0);
+             if (err < 0)
+                     return err;
+             /* BPF programs are run with preempt disabled, so
+              * synchronize_sched is sufficient even with
+              * RCU_PREEMPT.
+              */
+             synchronize_sched();
+             return 0;

I don't think it's necessary. sys_membarrier() can do this already and some folks use it exactly for this use case.

True --- implementation-wise, today. The point of the patch is to sort out contractual guarantees. membarrier isn't documented to have the BPF_SYNCHRONIZE effect right now, although it does. If we want to extend membarrier's contract, great, but that might make it more expensive than necessary for callers who, well, just want a memory barrier. It's worthwhile to have a dedicated BPF command for this task, especially considering the simplicity of the implementation.

#3 lore raw
In reply to Alexei Starovoitov
Quoted 35 lines · Alexei Starovoitov
Quoted 34 lines · Daniel Colascione

BPF_SYNCHRONIZE waits for any BPF programs active at the time of BPF_SYNCHRONIZE to complete, allowing userspace to ensure atomicity of RCU data structure operations with respect to active programs. For example, userspace can update a map->map entry to point to a new map, use BPF_SYNCHRONIZE to wait for any BPF programs using the old map to complete, and then drain the old map without fear that BPF programs may still be updating it.

---
 include/uapi/linux/bpf.h |  1 +
 kernel/bpf/syscall.c     | 14 ++++++++++++++
 2 files changed, 15 insertions(+)
include/uapi/linux/bpf.h +1 -0
index b7db3261c62d..4365c50e8055 100644
@@ -98,6 +98,7 @@ enum bpf_cmd {
 	BPF_BTF_LOAD,
 	BPF_BTF_GET_FD_BY_ID,
 	BPF_TASK_FD_QUERY,
+	BPF_SYNCHRONIZE,
 };
 
 enum bpf_map_type {
kernel/bpf/syscall.c +0 -0
index d10ecd78105f..60ec7811846e 100644
@@ -2272,6 +2272,20 @@ SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *, uattr, unsigned int, siz
 	if (sysctl_unprivileged_bpf_disabled && !capable(CAP_SYS_ADMIN))
 		return -EPERM;
 
Quoted 15 lines

kernel/bpf/syscall.c SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *, uattr, unsigned int, siz · line 2286

Quoted 12 lines
kernel/bpf/syscall.c +12 -0
+	if (cmd == BPF_SYNCHRONIZE) {
+		if (uattr != NULL || size != 0)
+			return -EINVAL;
+		err = security_bpf(cmd, NULL, 0);
+		if (err < 0)
+			return err;
+		/* BPF programs are run with preempt disabled, so
+		 * synchronize_sched is sufficient even with
+		 * RCU_PREEMPT.
+		 */
+		synchronize_sched();
+		return 0;

I don't think it's necessary. sys_membarrier() can do this already and some folks use it exactly for this use case.

Alexei, the use of sys_membarrier for this purpose seems kind of weird to me though. No where does the manpage say membarrier should be implemented this way so what happens if the implementation changes?

Further, membarrier manpage says that a memory barrier should be matched with a matching barrier. In this use case there is no matching barrier, so it makes it weirder.

Lastly, sys_membarrier seems will not work on nohz-full systems, so its a bit fragile to depend on it for this?

        case MEMBARRIER_CMD_GLOBAL:
                /* MEMBARRIER_CMD_GLOBAL is not compatible with nohz_full. */
                if (tick_nohz_full_enabled())
                        return -EINVAL;
                if (num_online_cpus() > 1)
                        synchronize_sched();
                return 0;

Adding Mathieu as well who I believe is author/maintainer of membarrier.

thanks!

- Joel

#4 lore raw
Quoted 73 lines · ----- On Jul 7, 2018, at 4:33 PM, Joel Fernandes [email protected] wrote:
Quoted 36 lines · Alexei Starovoitov
Quoted 35 lines · Daniel Colascione

BPF_SYNCHRONIZE waits for any BPF programs active at the time of BPF_SYNCHRONIZE to complete, allowing userspace to ensure atomicity of RCU data structure operations with respect to active programs. For example, userspace can update a map->map entry to point to a new map, use BPF_SYNCHRONIZE to wait for any BPF programs using the old map to complete, and then drain the old map without fear that BPF programs may still be updating it.

---
 include/uapi/linux/bpf.h |  1 +
 kernel/bpf/syscall.c     | 14 ++++++++++++++
 2 files changed, 15 insertions(+)
include/uapi/linux/bpf.h +1 -0
index b7db3261c62d..4365c50e8055 100644
@@ -98,6 +98,7 @@ enum bpf_cmd {
 	BPF_BTF_LOAD,
 	BPF_BTF_GET_FD_BY_ID,
 	BPF_TASK_FD_QUERY,
+	BPF_SYNCHRONIZE,
 };
 
 enum bpf_map_type {
kernel/bpf/syscall.c +0 -0
index d10ecd78105f..60ec7811846e 100644
@@ -2272,6 +2272,20 @@ SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *,

uattr, unsigned int, siz

kernel/bpf/syscall.c +0 -0
 	if (sysctl_unprivileged_bpf_disabled && !capable(CAP_SYS_ADMIN))
 		return -EPERM;
Quoted 15 lines

kernel/bpf/syscall.c SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *, uattr, unsigned int, siz · line 2286

Quoted 12 lines
kernel/bpf/syscall.c +12 -0
+	if (cmd == BPF_SYNCHRONIZE) {
+		if (uattr != NULL || size != 0)
+			return -EINVAL;
+		err = security_bpf(cmd, NULL, 0);
+		if (err < 0)
+			return err;
+		/* BPF programs are run with preempt disabled, so
+		 * synchronize_sched is sufficient even with
+		 * RCU_PREEMPT.
+		 */
+		synchronize_sched();
+		return 0;

I don't think it's necessary. sys_membarrier() can do this already and some folks use it exactly for this use case.

Alexei, the use of sys_membarrier for this purpose seems kind of weird to me though. No where does the manpage say membarrier should be implemented this way so what happens if the implementation changes?

Further, membarrier manpage says that a memory barrier should be matched with a matching barrier. In this use case there is no matching barrier, so it makes it weirder.

Lastly, sys_membarrier seems will not work on nohz-full systems, so its a bit fragile to depend on it for this?

       case MEMBARRIER_CMD_GLOBAL:
               /* MEMBARRIER_CMD_GLOBAL is not compatible with nohz_full. */
               if (tick_nohz_full_enabled())
                       return -EINVAL;
               if (num_online_cpus() > 1)
                       synchronize_sched();
               return 0;
Quoted 1 line

Adding Mathieu as well who I believe is author/maintainer of membarrier.

See commit 907565337
"Fix: Disable sys_membarrier when nohz_full is enabled"

"Userspace applications should be allowed to expect the membarrier system call with MEMBARRIER_CMD_SHARED command to issue memory barriers on nohz_full CPUs, but synchronize_sched() does not take those into account."

So AFAIU you'd want to re-use membarrier to issue synchronize_sched, and you only care about kernel preempt off critical sections.

Clearly bpf code does not run in user-space, so it would "work".

But the guarantees provided by membarrier are not to synchronize against preempt off per se. It's just that the current implementation happens to do that. The point of membarrier is to turn user-space memory barriers into compiler barriers.

If what you need is to wait for a RCU grace period for whatever RCU flavor ebpf is using, I would against using membarrier for this. I would rather recommend adding a dedicated BPF_SYNCHRONIZE so you won't leak implementation details to user-space, *and* you can eventually change you RCU implementation for e.g. SRCU in the future if needed.

Thanks,

Mathieu

Quoted 4 lines

thanks!

- Joel

Signature · 3 lines
Mathieu Desnoyers
EfficiOS Inc.
http://www.efficios.com
#5 lore raw
Quoted 95 lines · Mathieu Desnoyers
Quoted 73 lines · ----- On Jul 7, 2018, at 4:33 PM, Joel Fernandes [email protected] wrote:
Quoted 36 lines · Alexei Starovoitov
Quoted 35 lines · Daniel Colascione

BPF_SYNCHRONIZE waits for any BPF programs active at the time of BPF_SYNCHRONIZE to complete, allowing userspace to ensure atomicity of RCU data structure operations with respect to active programs. For example, userspace can update a map->map entry to point to a new map, use BPF_SYNCHRONIZE to wait for any BPF programs using the old map to complete, and then drain the old map without fear that BPF programs may still be updating it.

---
 include/uapi/linux/bpf.h |  1 +
 kernel/bpf/syscall.c     | 14 ++++++++++++++
 2 files changed, 15 insertions(+)
include/uapi/linux/bpf.h +1 -0
index b7db3261c62d..4365c50e8055 100644
@@ -98,6 +98,7 @@ enum bpf_cmd {
 	BPF_BTF_LOAD,
 	BPF_BTF_GET_FD_BY_ID,
 	BPF_TASK_FD_QUERY,
+	BPF_SYNCHRONIZE,
 };
 
 enum bpf_map_type {
kernel/bpf/syscall.c +0 -0
index d10ecd78105f..60ec7811846e 100644
@@ -2272,6 +2272,20 @@ SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *,

uattr, unsigned int, siz

kernel/bpf/syscall.c +0 -0
 	if (sysctl_unprivileged_bpf_disabled && !capable(CAP_SYS_ADMIN))
 		return -EPERM;
Quoted 15 lines

kernel/bpf/syscall.c SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *, uattr, unsigned int, siz · line 2286

Quoted 12 lines
kernel/bpf/syscall.c +12 -0
+	if (cmd == BPF_SYNCHRONIZE) {
+		if (uattr != NULL || size != 0)
+			return -EINVAL;
+		err = security_bpf(cmd, NULL, 0);
+		if (err < 0)
+			return err;
+		/* BPF programs are run with preempt disabled, so
+		 * synchronize_sched is sufficient even with
+		 * RCU_PREEMPT.
+		 */
+		synchronize_sched();
+		return 0;

I don't think it's necessary. sys_membarrier() can do this already and some folks use it exactly for this use case.

Alexei, the use of sys_membarrier for this purpose seems kind of weird to me though. No where does the manpage say membarrier should be implemented this way so what happens if the implementation changes?

Further, membarrier manpage says that a memory barrier should be matched with a matching barrier. In this use case there is no matching barrier, so it makes it weirder.

Lastly, sys_membarrier seems will not work on nohz-full systems, so its a bit fragile to depend on it for this?

       case MEMBARRIER_CMD_GLOBAL:
               /* MEMBARRIER_CMD_GLOBAL is not compatible with nohz_full. */
               if (tick_nohz_full_enabled())
                       return -EINVAL;
               if (num_online_cpus() > 1)
                       synchronize_sched();
               return 0;
Quoted 1 line

Adding Mathieu as well who I believe is author/maintainer of membarrier.

See commit 907565337
"Fix: Disable sys_membarrier when nohz_full is enabled"

"Userspace applications should be allowed to expect the membarrier system call with MEMBARRIER_CMD_SHARED command to issue memory barriers on nohz_full CPUs, but synchronize_sched() does not take those into account."

So AFAIU you'd want to re-use membarrier to issue synchronize_sched, and you only care about kernel preempt off critical sections.

Clearly bpf code does not run in user-space, so it would "work".

But the guarantees provided by membarrier are not to synchronize against preempt off per se. It's just that the current implementation happens to do that. The point of membarrier is to turn user-space memory barriers into compiler barriers.

Quoted 5 lines

If what you need is to wait for a RCU grace period for whatever RCU flavor ebpf is using, I would against using membarrier for this. I would rather recommend adding a dedicated BPF_SYNCHRONIZE so you won't leak implementation details to user-space, *and* you can eventually change you RCU implementation for e.g. SRCU in the future if needed.

The point about future changes to underlying bpf mechanisms is valid. There is work already on the way to reduce the scope of preempt_off+rcu_lock that currently lasts the whole prog. We will have new prog types that won't have such wrappers and will do rcu_lock/unlock and preempt on/off only when necessary.
So something like BPF_SYNCHRONIZE will break soon, since the kernel cannot have guarantees on when programs finish. Calling this command BPF_SYNCHRONIZE_PROG also won't make sense for the same reason.
What we can do it instead is to define synchronization barrier for programs accessing maps. May be call it something like:
BPF_SYNC_MAP_ACCESS ?
uapi/bpf.h would need to have extensive comment what this barrier is doing. Implementation should probably call synchronize_rcu() and not play games with synchronize_sched(), since that's going too much into implementation. Also should such sys_bpf command be root only?
I'm not sure whether dos attack can be made by spamming synchronize_rcu() and synchronize_sched() for that matter.

#6 lore raw
Quoted 111 lines · ----- On Jul 9, 2018, at 5:09 PM, Alexei Starovoitov…
Quoted 95 lines · Mathieu Desnoyers
Quoted 73 lines · ----- On Jul 7, 2018, at 4:33 PM, Joel Fernandes [email protected] wrote:
Quoted 36 lines · Alexei Starovoitov
Quoted 35 lines · Daniel Colascione

BPF_SYNCHRONIZE waits for any BPF programs active at the time of BPF_SYNCHRONIZE to complete, allowing userspace to ensure atomicity of RCU data structure operations with respect to active programs. For example, userspace can update a map->map entry to point to a new map, use BPF_SYNCHRONIZE to wait for any BPF programs using the old map to complete, and then drain the old map without fear that BPF programs may still be updating it.

---
 include/uapi/linux/bpf.h |  1 +
 kernel/bpf/syscall.c     | 14 ++++++++++++++
 2 files changed, 15 insertions(+)
include/uapi/linux/bpf.h +1 -0
index b7db3261c62d..4365c50e8055 100644
@@ -98,6 +98,7 @@ enum bpf_cmd {
 	BPF_BTF_LOAD,
 	BPF_BTF_GET_FD_BY_ID,
 	BPF_TASK_FD_QUERY,
+	BPF_SYNCHRONIZE,
 };
 
 enum bpf_map_type {
kernel/bpf/syscall.c +0 -0
index d10ecd78105f..60ec7811846e 100644
@@ -2272,6 +2272,20 @@ SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *,

uattr, unsigned int, siz

kernel/bpf/syscall.c +0 -0
 	if (sysctl_unprivileged_bpf_disabled && !capable(CAP_SYS_ADMIN))
 		return -EPERM;
Quoted 15 lines

kernel/bpf/syscall.c SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *, uattr, unsigned int, siz · line 2286

Quoted 12 lines
kernel/bpf/syscall.c +12 -0
+	if (cmd == BPF_SYNCHRONIZE) {
+		if (uattr != NULL || size != 0)
+			return -EINVAL;
+		err = security_bpf(cmd, NULL, 0);
+		if (err < 0)
+			return err;
+		/* BPF programs are run with preempt disabled, so
+		 * synchronize_sched is sufficient even with
+		 * RCU_PREEMPT.
+		 */
+		synchronize_sched();
+		return 0;

I don't think it's necessary. sys_membarrier() can do this already and some folks use it exactly for this use case.

Alexei, the use of sys_membarrier for this purpose seems kind of weird to me though. No where does the manpage say membarrier should be implemented this way so what happens if the implementation changes?

Further, membarrier manpage says that a memory barrier should be matched with a matching barrier. In this use case there is no matching barrier, so it makes it weirder.

Lastly, sys_membarrier seems will not work on nohz-full systems, so its a bit fragile to depend on it for this?

       case MEMBARRIER_CMD_GLOBAL:
               /* MEMBARRIER_CMD_GLOBAL is not compatible with nohz_full. */
               if (tick_nohz_full_enabled())
                       return -EINVAL;
               if (num_online_cpus() > 1)
                       synchronize_sched();
               return 0;
Quoted 1 line

Adding Mathieu as well who I believe is author/maintainer of membarrier.

See commit 907565337
"Fix: Disable sys_membarrier when nohz_full is enabled"

"Userspace applications should be allowed to expect the membarrier system call with MEMBARRIER_CMD_SHARED command to issue memory barriers on nohz_full CPUs, but synchronize_sched() does not take those into account."

So AFAIU you'd want to re-use membarrier to issue synchronize_sched, and you only care about kernel preempt off critical sections.

Clearly bpf code does not run in user-space, so it would "work".

But the guarantees provided by membarrier are not to synchronize against preempt off per se. It's just that the current implementation happens to do that. The point of membarrier is to turn user-space memory barriers into compiler barriers.

Quoted 5 lines

If what you need is to wait for a RCU grace period for whatever RCU flavor ebpf is using, I would against using membarrier for this. I would rather recommend adding a dedicated BPF_SYNCHRONIZE so you won't leak implementation details to user-space, *and* you can eventually change you RCU implementation for e.g. SRCU in the future if needed.

The point about future changes to underlying bpf mechanisms is valid. There is work already on the way to reduce the scope of preempt_off+rcu_lock that currently lasts the whole prog. We will have new prog types that won't have such wrappers and will do rcu_lock/unlock and preempt on/off only when necessary.
So something like BPF_SYNCHRONIZE will break soon, since the kernel cannot have guarantees on when programs finish. Calling this command BPF_SYNCHRONIZE_PROG also won't make sense for the same reason.
What we can do it instead is to define synchronization barrier for

Quoted 8 lines

programs accessing maps. May be call it something like:
BPF_SYNC_MAP_ACCESS ?
uapi/bpf.h would need to have extensive comment what this barrier is doing. Implementation should probably call synchronize_rcu() and not play games with synchronize_sched(), since that's going too much into implementation. Also should such sys_bpf command be root only?
I'm not sure whether dos attack can be made by spamming synchronize_rcu() and synchronize_sched() for that matter.

Adding Paul E. McKenney in CC. He may want to share his thoughts on the matter.

Thanks,

Mathieu

Signature · 3 lines
Mathieu Desnoyers
EfficiOS Inc.
http://www.efficios.com
#7 lore raw
Quoted 124 lines · Mathieu Desnoyers
Quoted 111 lines · ----- On Jul 9, 2018, at 5:09 PM, Alexei Starovoitov…
Quoted 95 lines · Mathieu Desnoyers
Quoted 73 lines · ----- On Jul 7, 2018, at 4:33 PM, Joel Fernandes [email protected] wrote:
Quoted 36 lines · Alexei Starovoitov
Quoted 35 lines · Daniel Colascione

BPF_SYNCHRONIZE waits for any BPF programs active at the time of BPF_SYNCHRONIZE to complete, allowing userspace to ensure atomicity of RCU data structure operations with respect to active programs. For example, userspace can update a map->map entry to point to a new map, use BPF_SYNCHRONIZE to wait for any BPF programs using the old map to complete, and then drain the old map without fear that BPF programs may still be updating it.

---
 include/uapi/linux/bpf.h |  1 +
 kernel/bpf/syscall.c     | 14 ++++++++++++++
 2 files changed, 15 insertions(+)
include/uapi/linux/bpf.h +1 -0
index b7db3261c62d..4365c50e8055 100644
@@ -98,6 +98,7 @@ enum bpf_cmd {
 	BPF_BTF_LOAD,
 	BPF_BTF_GET_FD_BY_ID,
 	BPF_TASK_FD_QUERY,
+	BPF_SYNCHRONIZE,
 };
 
 enum bpf_map_type {
kernel/bpf/syscall.c +0 -0
index d10ecd78105f..60ec7811846e 100644
@@ -2272,6 +2272,20 @@ SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *,

uattr, unsigned int, siz

kernel/bpf/syscall.c +0 -0
 	if (sysctl_unprivileged_bpf_disabled && !capable(CAP_SYS_ADMIN))
 		return -EPERM;
Quoted 15 lines

kernel/bpf/syscall.c SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *, uattr, unsigned int, siz · line 2286

Quoted 12 lines
kernel/bpf/syscall.c +12 -0
+	if (cmd == BPF_SYNCHRONIZE) {
+		if (uattr != NULL || size != 0)
+			return -EINVAL;
+		err = security_bpf(cmd, NULL, 0);
+		if (err < 0)
+			return err;
+		/* BPF programs are run with preempt disabled, so
+		 * synchronize_sched is sufficient even with
+		 * RCU_PREEMPT.
+		 */
+		synchronize_sched();
+		return 0;

I don't think it's necessary. sys_membarrier() can do this already and some folks use it exactly for this use case.

Alexei, the use of sys_membarrier for this purpose seems kind of weird to me though. No where does the manpage say membarrier should be implemented this way so what happens if the implementation changes?

Further, membarrier manpage says that a memory barrier should be matched with a matching barrier. In this use case there is no matching barrier, so it makes it weirder.

Lastly, sys_membarrier seems will not work on nohz-full systems, so its a bit fragile to depend on it for this?

       case MEMBARRIER_CMD_GLOBAL:
               /* MEMBARRIER_CMD_GLOBAL is not compatible with nohz_full. */
               if (tick_nohz_full_enabled())
                       return -EINVAL;
               if (num_online_cpus() > 1)
                       synchronize_sched();
               return 0;
Quoted 1 line

Adding Mathieu as well who I believe is author/maintainer of membarrier.

See commit 907565337
"Fix: Disable sys_membarrier when nohz_full is enabled"

"Userspace applications should be allowed to expect the membarrier system call with MEMBARRIER_CMD_SHARED command to issue memory barriers on nohz_full CPUs, but synchronize_sched() does not take those into account."

So AFAIU you'd want to re-use membarrier to issue synchronize_sched, and you only care about kernel preempt off critical sections.

Clearly bpf code does not run in user-space, so it would "work".

But the guarantees provided by membarrier are not to synchronize against preempt off per se. It's just that the current implementation happens to do that. The point of membarrier is to turn user-space memory barriers into compiler barriers.

Quoted 5 lines

If what you need is to wait for a RCU grace period for whatever RCU flavor ebpf is using, I would against using membarrier for this. I would rather recommend adding a dedicated BPF_SYNCHRONIZE so you won't leak implementation details to user-space, *and* you can eventually change you RCU implementation for e.g. SRCU in the future if needed.

The point about future changes to underlying bpf mechanisms is valid. There is work already on the way to reduce the scope of preempt_off+rcu_lock that currently lasts the whole prog. We will have new prog types that won't have such wrappers and will do rcu_lock/unlock and preempt on/off only when necessary.
So something like BPF_SYNCHRONIZE will break soon, since the kernel cannot have guarantees on when programs finish. Calling this command BPF_SYNCHRONIZE_PROG also won't make sense for the same reason.
What we can do it instead is to define synchronization barrier for

Quoted 8 lines

programs accessing maps. May be call it something like:
BPF_SYNC_MAP_ACCESS ?
uapi/bpf.h would need to have extensive comment what this barrier is doing. Implementation should probably call synchronize_rcu() and not play games with synchronize_sched(), since that's going too much into implementation. Also should such sys_bpf command be root only?
I'm not sure whether dos attack can be made by spamming synchronize_rcu() and synchronize_sched() for that matter.

Quoted 1 line

Adding Paul E. McKenney in CC. He may want to share his thoughts on the matter.

Let's see...

Spamming synchronize_rcu() and synchronize_sched() should be a non-event, at least aside from the CPUs doing the spamming. The reason for this is that a given task can only fire off a single synchronize_sched or synchronize_rcu() per few milliseconds, so you need a -lot- of tasks to have much effect, at which point the sheer number of tasks is much more a problem than the large number of outstanding synchronize_rcu() or synchronize_sched() invocations.

I very strongly agree that usermode should have a operation that synchronizes with whatever eBPF uses, rather than something that forces a specific type of RCU grace period.

Finally, in a few releases, synchronize_sched() will be retiring in favor of synchronize_rcu(), which will wait on preemption-disabled regions of code in addition to waiting on RCU read-side critical sections. Not a big deal, as I expect to enlist Coccinelle's aid in this.

Did I manage to hit all the high points?

Thanx, Paul

#8 lore raw
Quoted 141 lines · Paul E. McKenney
Quoted 124 lines · Mathieu Desnoyers
Quoted 111 lines · ----- On Jul 9, 2018, at 5:09 PM, Alexei Starovoitov…
Quoted 95 lines · Mathieu Desnoyers
Quoted 73 lines · ----- On Jul 7, 2018, at 4:33 PM, Joel Fernandes [email protected] wrote:
Quoted 36 lines · Alexei Starovoitov
Quoted 35 lines · Daniel Colascione

BPF_SYNCHRONIZE waits for any BPF programs active at the time of BPF_SYNCHRONIZE to complete, allowing userspace to ensure atomicity of RCU data structure operations with respect to active programs. For example, userspace can update a map->map entry to point to a new map, use BPF_SYNCHRONIZE to wait for any BPF programs using the old map to complete, and then drain the old map without fear that BPF programs may still be updating it.

---
 include/uapi/linux/bpf.h |  1 +
 kernel/bpf/syscall.c     | 14 ++++++++++++++
 2 files changed, 15 insertions(+)
include/uapi/linux/bpf.h +1 -0
index b7db3261c62d..4365c50e8055 100644
@@ -98,6 +98,7 @@ enum bpf_cmd {
 	BPF_BTF_LOAD,
 	BPF_BTF_GET_FD_BY_ID,
 	BPF_TASK_FD_QUERY,
+	BPF_SYNCHRONIZE,
 };
 
 enum bpf_map_type {
kernel/bpf/syscall.c +0 -0
index d10ecd78105f..60ec7811846e 100644
@@ -2272,6 +2272,20 @@ SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *,

uattr, unsigned int, siz

kernel/bpf/syscall.c +0 -0
 	if (sysctl_unprivileged_bpf_disabled && !capable(CAP_SYS_ADMIN))
 		return -EPERM;
Quoted 15 lines

kernel/bpf/syscall.c

Quoted 12 lines
kernel/bpf/syscall.c +12 -0
+	if (cmd == BPF_SYNCHRONIZE) {
+		if (uattr != NULL || size != 0)
+			return -EINVAL;
+		err = security_bpf(cmd, NULL, 0);
+		if (err < 0)
+			return err;
+		/* BPF programs are run with preempt disabled, so
+		 * synchronize_sched is sufficient even with
+		 * RCU_PREEMPT.
+		 */
+		synchronize_sched();
+		return 0;

I don't think it's necessary. sys_membarrier() can do this already and some folks use it exactly for this use case.

Alexei, the use of sys_membarrier for this purpose seems kind of weird to me though. No where does the manpage say membarrier should be implemented this way so what happens if the implementation changes?

Further, membarrier manpage says that a memory barrier should be matched with a matching barrier. In this use case there is no matching barrier, so it makes it weirder.

Lastly, sys_membarrier seems will not work on nohz-full systems, so its a bit fragile to depend on it for this?

       case MEMBARRIER_CMD_GLOBAL:
               /* MEMBARRIER_CMD_GLOBAL is not compatible with nohz_full. */
               if (tick_nohz_full_enabled())
                       return -EINVAL;
               if (num_online_cpus() > 1)
                       synchronize_sched();
               return 0;
Quoted 1 line

Adding Mathieu as well who I believe is author/maintainer of membarrier.

See commit 907565337
"Fix: Disable sys_membarrier when nohz_full is enabled"

"Userspace applications should be allowed to expect the membarrier system call with MEMBARRIER_CMD_SHARED command to issue memory barriers on nohz_full CPUs, but synchronize_sched() does not take those into account."

So AFAIU you'd want to re-use membarrier to issue synchronize_sched, and you only care about kernel preempt off critical sections.

Clearly bpf code does not run in user-space, so it would "work".

But the guarantees provided by membarrier are not to synchronize against preempt off per se. It's just that the current implementation happens to do that. The point of membarrier is to turn user-space memory barriers into compiler barriers.

Quoted 5 lines

If what you need is to wait for a RCU grace period for whatever RCU flavor ebpf is using, I would against using membarrier for this. I would rather recommend adding a dedicated BPF_SYNCHRONIZE so you won't leak implementation details to user-space, *and* you can eventually change you RCU implementation for e.g. SRCU in the future if needed.

The point about future changes to underlying bpf mechanisms is valid. There is work already on the way to reduce the scope of preempt_off+rcu_lock that currently lasts the whole prog. We will have new prog types that won't have such wrappers and will do rcu_lock/unlock and preempt on/off only when necessary.
So something like BPF_SYNCHRONIZE will break soon, since the kernel cannot have guarantees on when programs finish. Calling this command BPF_SYNCHRONIZE_PROG also won't make sense for the same reason.
What we can do it instead is to define synchronization barrier for

Quoted 8 lines

programs accessing maps. May be call it something like:
BPF_SYNC_MAP_ACCESS ?
uapi/bpf.h would need to have extensive comment what this barrier is doing. Implementation should probably call synchronize_rcu() and not play games with synchronize_sched(), since that's going too much into implementation. Also should such sys_bpf command be root only?
I'm not sure whether dos attack can be made by spamming synchronize_rcu() and synchronize_sched() for that matter.

Quoted 1 line

Adding Paul E. McKenney in CC. He may want to share his thoughts on the matter.

Let's see...

Spamming synchronize_rcu() and synchronize_sched() should be a non-event, at least aside from the CPUs doing the spamming. The reason for this is that a given task can only fire off a single synchronize_sched or synchronize_rcu() per few milliseconds, so you need a -lot- of tasks to have much effect, at which point the sheer number of tasks is much more a problem than the large number of outstanding synchronize_rcu() or synchronize_sched() invocations.

I very strongly agree that usermode should have a operation that synchronizes with whatever eBPF uses, rather than something that forces a specific type of RCU grace period.

Quoted 6 lines

Finally, in a few releases, synchronize_sched() will be retiring in favor of synchronize_rcu(), which will wait on preemption-disabled regions of code in addition to waiting on RCU read-side critical sections. Not a big deal, as I expect to enlist Coccinelle's aid in this.

Did I manage to hit all the high points?

Thanks. It's ok for new cmd being unpriv then.

#9 lore raw
Quoted 142 lines · Alexei Starovoitov
Quoted 141 lines · Paul E. McKenney
Quoted 124 lines · Mathieu Desnoyers
Quoted 111 lines · ----- On Jul 9, 2018, at 5:09 PM, Alexei Starovoitov…
Quoted 95 lines · Mathieu Desnoyers
Quoted 73 lines · ----- On Jul 7, 2018, at 4:33 PM, Joel Fernandes [email protected] wrote:
Quoted 36 lines · Alexei Starovoitov
Quoted 35 lines · Daniel Colascione

BPF_SYNCHRONIZE waits for any BPF programs active at the time of BPF_SYNCHRONIZE to complete, allowing userspace to ensure atomicity of RCU data structure operations with respect to active programs. For example, userspace can update a map->map entry to point to a new map, use BPF_SYNCHRONIZE to wait for any BPF programs using the old map to complete, and then drain the old map without fear that BPF programs may still be updating it.

---
 include/uapi/linux/bpf.h |  1 +
 kernel/bpf/syscall.c     | 14 ++++++++++++++
 2 files changed, 15 insertions(+)
include/uapi/linux/bpf.h +1 -0
index b7db3261c62d..4365c50e8055 100644
@@ -98,6 +98,7 @@ enum bpf_cmd {
 	BPF_BTF_LOAD,
 	BPF_BTF_GET_FD_BY_ID,
 	BPF_TASK_FD_QUERY,
+	BPF_SYNCHRONIZE,
 };
 
 enum bpf_map_type {
kernel/bpf/syscall.c +0 -0
index d10ecd78105f..60ec7811846e 100644
@@ -2272,6 +2272,20 @@ SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *,

uattr, unsigned int, siz

kernel/bpf/syscall.c +0 -0
 	if (sysctl_unprivileged_bpf_disabled && !capable(CAP_SYS_ADMIN))
 		return -EPERM;
Quoted 15 lines

kernel/bpf/syscall.c

Quoted 12 lines
kernel/bpf/syscall.c +12 -0
+	if (cmd == BPF_SYNCHRONIZE) {
+		if (uattr != NULL || size != 0)
+			return -EINVAL;
+		err = security_bpf(cmd, NULL, 0);
+		if (err < 0)
+			return err;
+		/* BPF programs are run with preempt disabled, so
+		 * synchronize_sched is sufficient even with
+		 * RCU_PREEMPT.
+		 */
+		synchronize_sched();
+		return 0;

I don't think it's necessary. sys_membarrier() can do this already and some folks use it exactly for this use case.

Alexei, the use of sys_membarrier for this purpose seems kind of weird to me though. No where does the manpage say membarrier should be implemented this way so what happens if the implementation changes?

Further, membarrier manpage says that a memory barrier should be matched with a matching barrier. In this use case there is no matching barrier, so it makes it weirder.

Lastly, sys_membarrier seems will not work on nohz-full systems, so its a bit fragile to depend on it for this?

       case MEMBARRIER_CMD_GLOBAL:
               /* MEMBARRIER_CMD_GLOBAL is not compatible with nohz_full. */
               if (tick_nohz_full_enabled())
                       return -EINVAL;
               if (num_online_cpus() > 1)
                       synchronize_sched();
               return 0;
Quoted 1 line

Adding Mathieu as well who I believe is author/maintainer of membarrier.

See commit 907565337
"Fix: Disable sys_membarrier when nohz_full is enabled"

"Userspace applications should be allowed to expect the membarrier system call with MEMBARRIER_CMD_SHARED command to issue memory barriers on nohz_full CPUs, but synchronize_sched() does not take those into account."

So AFAIU you'd want to re-use membarrier to issue synchronize_sched, and you only care about kernel preempt off critical sections.

Clearly bpf code does not run in user-space, so it would "work".

But the guarantees provided by membarrier are not to synchronize against preempt off per se. It's just that the current implementation happens to do that. The point of membarrier is to turn user-space memory barriers into compiler barriers.

Quoted 5 lines

If what you need is to wait for a RCU grace period for whatever RCU flavor ebpf is using, I would against using membarrier for this. I would rather recommend adding a dedicated BPF_SYNCHRONIZE so you won't leak implementation details to user-space, *and* you can eventually change you RCU implementation for e.g. SRCU in the future if needed.

The point about future changes to underlying bpf mechanisms is valid. There is work already on the way to reduce the scope of preempt_off+rcu_lock that currently lasts the whole prog. We will have new prog types that won't have such wrappers and will do rcu_lock/unlock and preempt on/off only when necessary.
So something like BPF_SYNCHRONIZE will break soon, since the kernel cannot have guarantees on when programs finish. Calling this command BPF_SYNCHRONIZE_PROG also won't make sense for the same reason.
What we can do it instead is to define synchronization barrier for

Quoted 8 lines

programs accessing maps. May be call it something like:
BPF_SYNC_MAP_ACCESS ?
uapi/bpf.h would need to have extensive comment what this barrier is doing. Implementation should probably call synchronize_rcu() and not play games with synchronize_sched(), since that's going too much into implementation. Also should such sys_bpf command be root only?
I'm not sure whether dos attack can be made by spamming synchronize_rcu() and synchronize_sched() for that matter.

Quoted 1 line

Adding Paul E. McKenney in CC. He may want to share his thoughts on the matter.

Let's see...

Spamming synchronize_rcu() and synchronize_sched() should be a non-event, at least aside from the CPUs doing the spamming. The reason for this is that a given task can only fire off a single synchronize_sched or synchronize_rcu() per few milliseconds, so you need a -lot- of tasks to have much effect, at which point the sheer number of tasks is much more a problem than the large number of outstanding synchronize_rcu() or synchronize_sched() invocations.

I very strongly agree that usermode should have a operation that synchronizes with whatever eBPF uses, rather than something that forces a specific type of RCU grace period.

Quoted 8 lines
Quoted 6 lines

Finally, in a few releases, synchronize_sched() will be retiring in favor of synchronize_rcu(), which will wait on preemption-disabled regions of code in addition to waiting on RCU read-side critical sections. Not a big deal, as I expect to enlist Coccinelle's aid in this.

Did I manage to hit all the high points?

Thanks. It's ok for new cmd being unpriv then.

Yes. After all, users can cause RCU far more pain by doing close(open()) in a tight loop. ;-)

Thanx, Paul

#10 lore raw
In reply to Alexei Starovoitov
Quoted 102 lines · Alexei Starovoitov
Quoted 95 lines · Mathieu Desnoyers
Quoted 73 lines · ----- On Jul 7, 2018, at 4:33 PM, Joel Fernandes [email protected] wrote:
Quoted 36 lines · Alexei Starovoitov
Quoted 35 lines · Daniel Colascione

BPF_SYNCHRONIZE waits for any BPF programs active at the time of BPF_SYNCHRONIZE to complete, allowing userspace to ensure atomicity of RCU data structure operations with respect to active programs. For example, userspace can update a map->map entry to point to a new map, use BPF_SYNCHRONIZE to wait for any BPF programs using the old map to complete, and then drain the old map without fear that BPF programs may still be updating it.

---
 include/uapi/linux/bpf.h |  1 +
 kernel/bpf/syscall.c     | 14 ++++++++++++++
 2 files changed, 15 insertions(+)
include/uapi/linux/bpf.h +1 -0
index b7db3261c62d..4365c50e8055 100644
@@ -98,6 +98,7 @@ enum bpf_cmd {
         BPF_BTF_LOAD,
         BPF_BTF_GET_FD_BY_ID,
         BPF_TASK_FD_QUERY,
+        BPF_SYNCHRONIZE,
 };
 
 enum bpf_map_type {
kernel/bpf/syscall.c +0 -0
index d10ecd78105f..60ec7811846e 100644
@@ -2272,6 +2272,20 @@ SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *,

uattr, unsigned int, siz

         if (sysctl_unprivileged_bpf_disabled && !capable(CAP_SYS_ADMIN))
                 return -EPERM;
Quoted 15 lines

kernel/bpf/syscall.c SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *, uattr, unsigned int, siz · line 2286

Quoted 12 lines
kernel/bpf/syscall.c +12 -0
+        if (cmd == BPF_SYNCHRONIZE) {
+                if (uattr != NULL || size != 0)
+                        return -EINVAL;
+                err = security_bpf(cmd, NULL, 0);
+                if (err < 0)
+                        return err;
+                /* BPF programs are run with preempt disabled, so
+                 * synchronize_sched is sufficient even with
+                 * RCU_PREEMPT.
+                 */
+                synchronize_sched();
+                return 0;

I don't think it's necessary. sys_membarrier() can do this already and some folks use it exactly for this use case.

Alexei, the use of sys_membarrier for this purpose seems kind of weird to me though. No where does the manpage say membarrier should be implemented this way so what happens if the implementation changes?

Further, membarrier manpage says that a memory barrier should be matched with a matching barrier. In this use case there is no matching barrier, so it makes it weirder.

Lastly, sys_membarrier seems will not work on nohz-full systems, so its a bit fragile to depend on it for this?

       case MEMBARRIER_CMD_GLOBAL:
               /* MEMBARRIER_CMD_GLOBAL is not compatible with nohz_full. */
               if (tick_nohz_full_enabled())
                       return -EINVAL;
               if (num_online_cpus() > 1)
                       synchronize_sched();
               return 0;
Quoted 1 line

Adding Mathieu as well who I believe is author/maintainer of membarrier.

See commit 907565337
"Fix: Disable sys_membarrier when nohz_full is enabled"

"Userspace applications should be allowed to expect the membarrier system call with MEMBARRIER_CMD_SHARED command to issue memory barriers on nohz_full CPUs, but synchronize_sched() does not take those into account."

So AFAIU you'd want to re-use membarrier to issue synchronize_sched, and you only care about kernel preempt off critical sections.

Clearly bpf code does not run in user-space, so it would "work".

But the guarantees provided by membarrier are not to synchronize against preempt off per se. It's just that the current implementation happens to do that. The point of membarrier is to turn user-space memory barriers into compiler barriers.

Quoted 5 lines

If what you need is to wait for a RCU grace period for whatever RCU flavor ebpf is using, I would against using membarrier for this. I would rather recommend adding a dedicated BPF_SYNCHRONIZE so you won't leak implementation details to user-space, *and* you can eventually change you RCU implementation for e.g. SRCU in the future if needed.

Quoted 11 lines

The point about future changes to underlying bpf mechanisms is valid. There is work already on the way to reduce the scope of preempt_off+rcu_lock that currently lasts the whole prog. We will have new prog types that won't have such wrappers and will do rcu_lock/unlock and preempt on/off only when necessary.
So something like BPF_SYNCHRONIZE will break soon, since the kernel cannot have guarantees on when programs finish. Calling this command BPF_SYNCHRONIZE_PROG also won't make sense for the same reason.
What we can do it instead is to define synchronization barrier for programs accessing maps. May be call it something like:
BPF_SYNC_MAP_ACCESS ?

I'm not sure what you're proposing. In the case the commit message describes, a user-space program that wants to "drain" a map needs to be confident that the map won't change under it, even across multiple bpf system calls on that map. One way of doing that is to ensure that nothing that could possibly hold a reference to that map is still running. Are you proposing some kind of refcount-draining approach? Simple locking won't work, since BPF programs can't block, and I don't see right now how a simple barrier would help.

Quoted 3 lines

Also should such sys_bpf command be root only?
I'm not sure whether dos attack can be made by spamming synchronize_rcu() and synchronize_sched() for that matter.

membarrier being globally available seems not to have caused any kind of DoS catastrophe.

#11 lore raw
Quoted 116 lines · Daniel Colascione
Quoted 102 lines · Alexei Starovoitov
Quoted 95 lines · Mathieu Desnoyers
Quoted 73 lines · ----- On Jul 7, 2018, at 4:33 PM, Joel Fernandes [email protected] wrote:
Quoted 36 lines · Alexei Starovoitov
Quoted 35 lines · Daniel Colascione

BPF_SYNCHRONIZE waits for any BPF programs active at the time of BPF_SYNCHRONIZE to complete, allowing userspace to ensure atomicity of RCU data structure operations with respect to active programs. For example, userspace can update a map->map entry to point to a new map, use BPF_SYNCHRONIZE to wait for any BPF programs using the old map to complete, and then drain the old map without fear that BPF programs may still be updating it.

---
 include/uapi/linux/bpf.h |  1 +
 kernel/bpf/syscall.c     | 14 ++++++++++++++
 2 files changed, 15 insertions(+)
include/uapi/linux/bpf.h +1 -0
index b7db3261c62d..4365c50e8055 100644
@@ -98,6 +98,7 @@ enum bpf_cmd {
         BPF_BTF_LOAD,
         BPF_BTF_GET_FD_BY_ID,
         BPF_TASK_FD_QUERY,
+        BPF_SYNCHRONIZE,
 };
 
 enum bpf_map_type {
kernel/bpf/syscall.c +0 -0
index d10ecd78105f..60ec7811846e 100644
@@ -2272,6 +2272,20 @@ SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *,

uattr, unsigned int, siz

         if (sysctl_unprivileged_bpf_disabled && !capable(CAP_SYS_ADMIN))
                 return -EPERM;
Quoted 15 lines

kernel/bpf/syscall.c SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *, uattr, unsigned int, siz · line 2286

Quoted 12 lines
kernel/bpf/syscall.c +12 -0
+        if (cmd == BPF_SYNCHRONIZE) {
+                if (uattr != NULL || size != 0)
+                        return -EINVAL;
+                err = security_bpf(cmd, NULL, 0);
+                if (err < 0)
+                        return err;
+                /* BPF programs are run with preempt disabled, so
+                 * synchronize_sched is sufficient even with
+                 * RCU_PREEMPT.
+                 */
+                synchronize_sched();
+                return 0;

I don't think it's necessary. sys_membarrier() can do this already and some folks use it exactly for this use case.

Alexei, the use of sys_membarrier for this purpose seems kind of weird to me though. No where does the manpage say membarrier should be implemented this way so what happens if the implementation changes?

Further, membarrier manpage says that a memory barrier should be matched with a matching barrier. In this use case there is no matching barrier, so it makes it weirder.

Lastly, sys_membarrier seems will not work on nohz-full systems, so its a bit fragile to depend on it for this?

       case MEMBARRIER_CMD_GLOBAL:
               /* MEMBARRIER_CMD_GLOBAL is not compatible with nohz_full. */
               if (tick_nohz_full_enabled())
                       return -EINVAL;
               if (num_online_cpus() > 1)
                       synchronize_sched();
               return 0;
Quoted 1 line

Adding Mathieu as well who I believe is author/maintainer of membarrier.

See commit 907565337
"Fix: Disable sys_membarrier when nohz_full is enabled"

"Userspace applications should be allowed to expect the membarrier system call with MEMBARRIER_CMD_SHARED command to issue memory barriers on nohz_full CPUs, but synchronize_sched() does not take those into account."

So AFAIU you'd want to re-use membarrier to issue synchronize_sched, and you only care about kernel preempt off critical sections.

Clearly bpf code does not run in user-space, so it would "work".

But the guarantees provided by membarrier are not to synchronize against preempt off per se. It's just that the current implementation happens to do that. The point of membarrier is to turn user-space memory barriers into compiler barriers.

Quoted 5 lines

If what you need is to wait for a RCU grace period for whatever RCU flavor ebpf is using, I would against using membarrier for this. I would rather recommend adding a dedicated BPF_SYNCHRONIZE so you won't leak implementation details to user-space, *and* you can eventually change you RCU implementation for e.g. SRCU in the future if needed.

Quoted 11 lines

The point about future changes to underlying bpf mechanisms is valid. There is work already on the way to reduce the scope of preempt_off+rcu_lock that currently lasts the whole prog. We will have new prog types that won't have such wrappers and will do rcu_lock/unlock and preempt on/off only when necessary.
So something like BPF_SYNCHRONIZE will break soon, since the kernel cannot have guarantees on when programs finish. Calling this command BPF_SYNCHRONIZE_PROG also won't make sense for the same reason.
What we can do it instead is to define synchronization barrier for programs accessing maps. May be call it something like:
BPF_SYNC_MAP_ACCESS ?

Quoted 8 lines

I'm not sure what you're proposing. In the case the commit message describes, a user-space program that wants to "drain" a map needs to be confident that the map won't change under it, even across multiple bpf system calls on that map. One way of doing that is to ensure that nothing that could possibly hold a reference to that map is still running. Are you proposing some kind of refcount-draining approach? Simple locking won't work, since BPF programs can't block, and I don't see right now how a simple barrier would help.

I'm proposing few changes for your patch:
s/BPF_SYNCHRONIZE/BPF_SYNC_MAP_ACCESS/
and s/synchronize_sched/synchronize_rcu/
with detailed comment in uapi/bpf.h that has an example why folks would want to use this new cmd.
I think the bpf maps will be rcu protected for foreseeable future even when rcu_read_lock/unlock will be done by the programs instead of kernel wrappers.

#12 lore raw
Quoted 126 lines · Alexei Starovoitov
Quoted 116 lines · Daniel Colascione
Quoted 102 lines · Alexei Starovoitov
Quoted 95 lines · Mathieu Desnoyers
Quoted 73 lines · ----- On Jul 7, 2018, at 4:33 PM, Joel Fernandes [email protected] wrote:
Quoted 36 lines · Alexei Starovoitov
Quoted 35 lines · Daniel Colascione

BPF_SYNCHRONIZE waits for any BPF programs active at the time of BPF_SYNCHRONIZE to complete, allowing userspace to ensure atomicity of RCU data structure operations with respect to active programs. For example, userspace can update a map->map entry to point to a new map, use BPF_SYNCHRONIZE to wait for any BPF programs using the old map to complete, and then drain the old map without fear that BPF programs may still be updating it.

---
 include/uapi/linux/bpf.h |  1 +
 kernel/bpf/syscall.c     | 14 ++++++++++++++
 2 files changed, 15 insertions(+)
include/uapi/linux/bpf.h +1 -0
index b7db3261c62d..4365c50e8055 100644
@@ -98,6 +98,7 @@ enum bpf_cmd {
         BPF_BTF_LOAD,
         BPF_BTF_GET_FD_BY_ID,
         BPF_TASK_FD_QUERY,
+        BPF_SYNCHRONIZE,
 };
 
 enum bpf_map_type {
kernel/bpf/syscall.c +0 -0
index d10ecd78105f..60ec7811846e 100644
@@ -2272,6 +2272,20 @@ SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *,

uattr, unsigned int, siz

         if (sysctl_unprivileged_bpf_disabled && !capable(CAP_SYS_ADMIN))
                 return -EPERM;
Quoted 15 lines

kernel/bpf/syscall.c

Quoted 12 lines
kernel/bpf/syscall.c +12 -0
+        if (cmd == BPF_SYNCHRONIZE) {
+                if (uattr != NULL || size != 0)
+                        return -EINVAL;
+                err = security_bpf(cmd, NULL, 0);
+                if (err < 0)
+                        return err;
+                /* BPF programs are run with preempt disabled, so
+                 * synchronize_sched is sufficient even with
+                 * RCU_PREEMPT.
+                 */
+                synchronize_sched();
+                return 0;

I don't think it's necessary. sys_membarrier() can do this already and some folks use it exactly for this use case.

Alexei, the use of sys_membarrier for this purpose seems kind of weird to me though. No where does the manpage say membarrier should be implemented this way so what happens if the implementation changes?

Further, membarrier manpage says that a memory barrier should be matched with a matching barrier. In this use case there is no matching barrier, so it makes it weirder.

Lastly, sys_membarrier seems will not work on nohz-full systems, so its a bit fragile to depend on it for this?

       case MEMBARRIER_CMD_GLOBAL:
               /* MEMBARRIER_CMD_GLOBAL is not compatible with nohz_full. */
               if (tick_nohz_full_enabled())
                       return -EINVAL;
               if (num_online_cpus() > 1)
                       synchronize_sched();
               return 0;
Quoted 1 line

Adding Mathieu as well who I believe is author/maintainer of membarrier.

See commit 907565337
"Fix: Disable sys_membarrier when nohz_full is enabled"

"Userspace applications should be allowed to expect the membarrier system call with MEMBARRIER_CMD_SHARED command to issue memory barriers on nohz_full CPUs, but synchronize_sched() does not take those into account."

So AFAIU you'd want to re-use membarrier to issue synchronize_sched, and you only care about kernel preempt off critical sections.

Clearly bpf code does not run in user-space, so it would "work".

But the guarantees provided by membarrier are not to synchronize against preempt off per se. It's just that the current implementation happens to do that. The point of membarrier is to turn user-space memory barriers into compiler barriers.

Quoted 5 lines

If what you need is to wait for a RCU grace period for whatever RCU flavor ebpf is using, I would against using membarrier for this. I would rather recommend adding a dedicated BPF_SYNCHRONIZE so you won't leak implementation details to user-space, *and* you can eventually change you RCU implementation for e.g. SRCU in the future if needed.

Quoted 11 lines

The point about future changes to underlying bpf mechanisms is valid. There is work already on the way to reduce the scope of preempt_off+rcu_lock that currently lasts the whole prog. We will have new prog types that won't have such wrappers and will do rcu_lock/unlock and preempt on/off only when necessary.
So something like BPF_SYNCHRONIZE will break soon, since the kernel cannot have guarantees on when programs finish. Calling this command BPF_SYNCHRONIZE_PROG also won't make sense for the same reason.
What we can do it instead is to define synchronization barrier for programs accessing maps. May be call it something like:
BPF_SYNC_MAP_ACCESS ?

Quoted 8 lines

I'm not sure what you're proposing. In the case the commit message describes, a user-space program that wants to "drain" a map needs to be confident that the map won't change under it, even across multiple bpf system calls on that map. One way of doing that is to ensure that nothing that could possibly hold a reference to that map is still running. Are you proposing some kind of refcount-draining approach? Simple locking won't work, since BPF programs can't block, and I don't see right now how a simple barrier would help.

Quoted 5 lines

I'm proposing few changes for your patch:
s/BPF_SYNCHRONIZE/BPF_SYNC_MAP_ACCESS/
and s/synchronize_sched/synchronize_rcu/
with detailed comment in uapi/bpf.h that has an example why folks would want to use this new cmd.

Thanks for clarifying.

Quoted 3 lines

I think the bpf maps will be rcu protected for foreseeable future even when rcu_read_lock/unlock will be done by the programs instead of kernel wrappers.

Can we guarantee that we always obtain a map reference and dispose of that reference inside the same critical section? If so, can BPF programs then disable preemption for as long as they'd like?

#13 lore raw
Quoted 134 lines · Daniel Colascione
Quoted 126 lines · Alexei Starovoitov
Quoted 116 lines · Daniel Colascione
Quoted 102 lines · Alexei Starovoitov
Quoted 95 lines · Mathieu Desnoyers
Quoted 73 lines · ----- On Jul 7, 2018, at 4:33 PM, Joel Fernandes [email protected] wrote:
Quoted 36 lines · Alexei Starovoitov
Quoted 35 lines · Daniel Colascione

BPF_SYNCHRONIZE waits for any BPF programs active at the time of BPF_SYNCHRONIZE to complete, allowing userspace to ensure atomicity of RCU data structure operations with respect to active programs. For example, userspace can update a map->map entry to point to a new map, use BPF_SYNCHRONIZE to wait for any BPF programs using the old map to complete, and then drain the old map without fear that BPF programs may still be updating it.

---
 include/uapi/linux/bpf.h |  1 +
 kernel/bpf/syscall.c     | 14 ++++++++++++++
 2 files changed, 15 insertions(+)
include/uapi/linux/bpf.h +1 -0
index b7db3261c62d..4365c50e8055 100644
@@ -98,6 +98,7 @@ enum bpf_cmd {
         BPF_BTF_LOAD,
         BPF_BTF_GET_FD_BY_ID,
         BPF_TASK_FD_QUERY,
+        BPF_SYNCHRONIZE,
 };
 
 enum bpf_map_type {
kernel/bpf/syscall.c +0 -0
index d10ecd78105f..60ec7811846e 100644
@@ -2272,6 +2272,20 @@ SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *,

uattr, unsigned int, siz

         if (sysctl_unprivileged_bpf_disabled && !capable(CAP_SYS_ADMIN))
                 return -EPERM;
Quoted 15 lines

kernel/bpf/syscall.c

Quoted 12 lines
kernel/bpf/syscall.c +12 -0
+        if (cmd == BPF_SYNCHRONIZE) {
+                if (uattr != NULL || size != 0)
+                        return -EINVAL;
+                err = security_bpf(cmd, NULL, 0);
+                if (err < 0)
+                        return err;
+                /* BPF programs are run with preempt disabled, so
+                 * synchronize_sched is sufficient even with
+                 * RCU_PREEMPT.
+                 */
+                synchronize_sched();
+                return 0;

I don't think it's necessary. sys_membarrier() can do this already and some folks use it exactly for this use case.

Alexei, the use of sys_membarrier for this purpose seems kind of weird to me though. No where does the manpage say membarrier should be implemented this way so what happens if the implementation changes?

Further, membarrier manpage says that a memory barrier should be matched with a matching barrier. In this use case there is no matching barrier, so it makes it weirder.

Lastly, sys_membarrier seems will not work on nohz-full systems, so its a bit fragile to depend on it for this?

       case MEMBARRIER_CMD_GLOBAL:
               /* MEMBARRIER_CMD_GLOBAL is not compatible with nohz_full. */
               if (tick_nohz_full_enabled())
                       return -EINVAL;
               if (num_online_cpus() > 1)
                       synchronize_sched();
               return 0;
Quoted 1 line

Adding Mathieu as well who I believe is author/maintainer of membarrier.

See commit 907565337
"Fix: Disable sys_membarrier when nohz_full is enabled"

"Userspace applications should be allowed to expect the membarrier system call with MEMBARRIER_CMD_SHARED command to issue memory barriers on nohz_full CPUs, but synchronize_sched() does not take those into account."

So AFAIU you'd want to re-use membarrier to issue synchronize_sched, and you only care about kernel preempt off critical sections.

Clearly bpf code does not run in user-space, so it would "work".

But the guarantees provided by membarrier are not to synchronize against preempt off per se. It's just that the current implementation happens to do that. The point of membarrier is to turn user-space memory barriers into compiler barriers.

Quoted 5 lines

If what you need is to wait for a RCU grace period for whatever RCU flavor ebpf is using, I would against using membarrier for this. I would rather recommend adding a dedicated BPF_SYNCHRONIZE so you won't leak implementation details to user-space, *and* you can eventually change you RCU implementation for e.g. SRCU in the future if needed.

Quoted 11 lines

The point about future changes to underlying bpf mechanisms is valid. There is work already on the way to reduce the scope of preempt_off+rcu_lock that currently lasts the whole prog. We will have new prog types that won't have such wrappers and will do rcu_lock/unlock and preempt on/off only when necessary.
So something like BPF_SYNCHRONIZE will break soon, since the kernel cannot have guarantees on when programs finish. Calling this command BPF_SYNCHRONIZE_PROG also won't make sense for the same reason.
What we can do it instead is to define synchronization barrier for programs accessing maps. May be call it something like:
BPF_SYNC_MAP_ACCESS ?

Quoted 8 lines

I'm not sure what you're proposing. In the case the commit message describes, a user-space program that wants to "drain" a map needs to be confident that the map won't change under it, even across multiple bpf system calls on that map. One way of doing that is to ensure that nothing that could possibly hold a reference to that map is still running. Are you proposing some kind of refcount-draining approach? Simple locking won't work, since BPF programs can't block, and I don't see right now how a simple barrier would help.

Quoted 5 lines

I'm proposing few changes for your patch:
s/BPF_SYNCHRONIZE/BPF_SYNC_MAP_ACCESS/
and s/synchronize_sched/synchronize_rcu/
with detailed comment in uapi/bpf.h that has an example why folks would want to use this new cmd.

Quoted 8 lines

Thanks for clarifying.

Quoted 3 lines

I think the bpf maps will be rcu protected for foreseeable future even when rcu_read_lock/unlock will be done by the programs instead of kernel wrappers.

Can we guarantee that we always obtain a map reference and dispose of that reference inside the same critical section?

yep. the verifier will guarantee that.

Quoted 2 lines

If so, can BPF
programs then disable preemption for as long as they'd like?

you mean after the finish? no. only while running.
The verifier will match things like lookup/release, lock/unlock, preempt on/off and will make sure there is no dangling preempt disable after program returns.

#14 lore raw
Quoted 145 lines · Alexei Starovoitov
Quoted 134 lines · Daniel Colascione
Quoted 126 lines · Alexei Starovoitov
Quoted 116 lines · Daniel Colascione
Quoted 102 lines · Alexei Starovoitov
Quoted 95 lines · Mathieu Desnoyers
Quoted 73 lines · ----- On Jul 7, 2018, at 4:33 PM, Joel Fernandes [email protected] wrote:
Quoted 36 lines · Alexei Starovoitov
Quoted 35 lines · Daniel Colascione

BPF_SYNCHRONIZE waits for any BPF programs active at the time of BPF_SYNCHRONIZE to complete, allowing userspace to ensure atomicity of RCU data structure operations with respect to active programs. For example, userspace can update a map->map entry to point to a new map, use BPF_SYNCHRONIZE to wait for any BPF programs using the old map to complete, and then drain the old map without fear that BPF programs may still be updating it.

---
 include/uapi/linux/bpf.h |  1 +
 kernel/bpf/syscall.c     | 14 ++++++++++++++
 2 files changed, 15 insertions(+)
include/uapi/linux/bpf.h +1 -0
index b7db3261c62d..4365c50e8055 100644
@@ -98,6 +98,7 @@ enum bpf_cmd {
         BPF_BTF_LOAD,
         BPF_BTF_GET_FD_BY_ID,
         BPF_TASK_FD_QUERY,
+        BPF_SYNCHRONIZE,
 };
 
 enum bpf_map_type {
kernel/bpf/syscall.c +0 -0
index d10ecd78105f..60ec7811846e 100644
@@ -2272,6 +2272,20 @@ SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *,

uattr, unsigned int, siz

         if (sysctl_unprivileged_bpf_disabled && !capable(CAP_SYS_ADMIN))
                 return -EPERM;
Quoted 15 lines

kernel/bpf/syscall.c

Quoted 12 lines
kernel/bpf/syscall.c +12 -0
+        if (cmd == BPF_SYNCHRONIZE) {
+                if (uattr != NULL || size != 0)
+                        return -EINVAL;
+                err = security_bpf(cmd, NULL, 0);
+                if (err < 0)
+                        return err;
+                /* BPF programs are run with preempt disabled, so
+                 * synchronize_sched is sufficient even with
+                 * RCU_PREEMPT.
+                 */
+                synchronize_sched();
+                return 0;

I don't think it's necessary. sys_membarrier() can do this already and some folks use it exactly for this use case.

Alexei, the use of sys_membarrier for this purpose seems kind of weird to me though. No where does the manpage say membarrier should be implemented this way so what happens if the implementation changes?

Further, membarrier manpage says that a memory barrier should be matched with a matching barrier. In this use case there is no matching barrier, so it makes it weirder.

Lastly, sys_membarrier seems will not work on nohz-full systems, so its a bit fragile to depend on it for this?

       case MEMBARRIER_CMD_GLOBAL:
               /* MEMBARRIER_CMD_GLOBAL is not compatible with nohz_full. */
               if (tick_nohz_full_enabled())
                       return -EINVAL;
               if (num_online_cpus() > 1)
                       synchronize_sched();
               return 0;
Quoted 1 line

Adding Mathieu as well who I believe is author/maintainer of membarrier.

See commit 907565337
"Fix: Disable sys_membarrier when nohz_full is enabled"

"Userspace applications should be allowed to expect the membarrier system call with MEMBARRIER_CMD_SHARED command to issue memory barriers on nohz_full CPUs, but synchronize_sched() does not take those into account."

So AFAIU you'd want to re-use membarrier to issue synchronize_sched, and you only care about kernel preempt off critical sections.

Clearly bpf code does not run in user-space, so it would "work".

But the guarantees provided by membarrier are not to synchronize against preempt off per se. It's just that the current implementation happens to do that. The point of membarrier is to turn user-space memory barriers into compiler barriers.

Quoted 5 lines

If what you need is to wait for a RCU grace period for whatever RCU flavor ebpf is using, I would against using membarrier for this. I would rather recommend adding a dedicated BPF_SYNCHRONIZE so you won't leak implementation details to user-space, *and* you can eventually change you RCU implementation for e.g. SRCU in the future if needed.

Quoted 11 lines

The point about future changes to underlying bpf mechanisms is valid. There is work already on the way to reduce the scope of preempt_off+rcu_lock that currently lasts the whole prog. We will have new prog types that won't have such wrappers and will do rcu_lock/unlock and preempt on/off only when necessary.
So something like BPF_SYNCHRONIZE will break soon, since the kernel cannot have guarantees on when programs finish. Calling this command BPF_SYNCHRONIZE_PROG also won't make sense for the same reason.
What we can do it instead is to define synchronization barrier for programs accessing maps. May be call it something like:
BPF_SYNC_MAP_ACCESS ?

Quoted 8 lines

I'm not sure what you're proposing. In the case the commit message describes, a user-space program that wants to "drain" a map needs to be confident that the map won't change under it, even across multiple bpf system calls on that map. One way of doing that is to ensure that nothing that could possibly hold a reference to that map is still running. Are you proposing some kind of refcount-draining approach? Simple locking won't work, since BPF programs can't block, and I don't see right now how a simple barrier would help.

Quoted 5 lines

I'm proposing few changes for your patch:
s/BPF_SYNCHRONIZE/BPF_SYNC_MAP_ACCESS/
and s/synchronize_sched/synchronize_rcu/
with detailed comment in uapi/bpf.h that has an example why folks would want to use this new cmd.

Quoted 8 lines

Thanks for clarifying.

Quoted 3 lines

I think the bpf maps will be rcu protected for foreseeable future even when rcu_read_lock/unlock will be done by the programs instead of kernel wrappers.

Can we guarantee that we always obtain a map reference and dispose of that reference inside the same critical section?

Quoted 9 lines

yep. the verifier will guarantee that.

Quoted 2 lines

If so, can BPF
programs then disable preemption for as long as they'd like?

you mean after the finish? no. only while running.
The verifier will match things like lookup/release, lock/unlock, preempt on/off and will make sure there is no dangling preempt disable after program returns.

It might be a silly question but does this new bpf program type provide a way to customize where to hold the rcu_lock/unlock and enable/disable preemption when creating the program? Since the BPF_SYNC_MAP_ACCESS cmd sounds not very useful if it only protect each single map access instead of the whole program. For example, sometimes we have to read the same map multiple times when running a eBPF program and if the userspace changed the map value between two reads, it may cause troubles. It would be great if we can have a RCU lock on a block of eBPF program and in that way I think BPF_SYNC_MAP_ACCESS cmd should be enough in handling userspace kernel racing problems.

Thanks
Chenbo Feng

#15 lore raw
Quoted 156 lines · Chenbo Feng
Quoted 145 lines · Alexei Starovoitov
Quoted 134 lines · Daniel Colascione
Quoted 126 lines · Alexei Starovoitov
Quoted 116 lines · Daniel Colascione
Quoted 102 lines · Alexei Starovoitov
Quoted 95 lines · Mathieu Desnoyers
Quoted 73 lines · ----- On Jul 7, 2018, at 4:33 PM, Joel Fernandes [email protected] wrote:
Quoted 36 lines · Alexei Starovoitov
Quoted 35 lines · Daniel Colascione

BPF_SYNCHRONIZE waits for any BPF programs active at the time of BPF_SYNCHRONIZE to complete, allowing userspace to ensure atomicity of RCU data structure operations with respect to active programs. For example, userspace can update a map->map entry to point to a new map, use BPF_SYNCHRONIZE to wait for any BPF programs using the old map to complete, and then drain the old map without fear that BPF programs may still be updating it.

---
 include/uapi/linux/bpf.h |  1 +
 kernel/bpf/syscall.c     | 14 ++++++++++++++
 2 files changed, 15 insertions(+)
include/uapi/linux/bpf.h +1 -0
index b7db3261c62d..4365c50e8055 100644
@@ -98,6 +98,7 @@ enum bpf_cmd {
         BPF_BTF_LOAD,
         BPF_BTF_GET_FD_BY_ID,
         BPF_TASK_FD_QUERY,
+        BPF_SYNCHRONIZE,
 };
 
 enum bpf_map_type {
kernel/bpf/syscall.c +0 -0
index d10ecd78105f..60ec7811846e 100644
@@ -2272,6 +2272,20 @@ SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *,

uattr, unsigned int, siz

         if (sysctl_unprivileged_bpf_disabled && !capable(CAP_SYS_ADMIN))
                 return -EPERM;
Quoted 15 lines

kernel/bpf/syscall.c

Quoted 12 lines
kernel/bpf/syscall.c +12 -0
+        if (cmd == BPF_SYNCHRONIZE) {
+                if (uattr != NULL || size != 0)
+                        return -EINVAL;
+                err = security_bpf(cmd, NULL, 0);
+                if (err < 0)
+                        return err;
+                /* BPF programs are run with preempt disabled, so
+                 * synchronize_sched is sufficient even with
+                 * RCU_PREEMPT.
+                 */
+                synchronize_sched();
+                return 0;

I don't think it's necessary. sys_membarrier() can do this already and some folks use it exactly for this use case.

Alexei, the use of sys_membarrier for this purpose seems kind of weird to me though. No where does the manpage say membarrier should be implemented this way so what happens if the implementation changes?

Further, membarrier manpage says that a memory barrier should be matched with a matching barrier. In this use case there is no matching barrier, so it makes it weirder.

Lastly, sys_membarrier seems will not work on nohz-full systems, so its a bit fragile to depend on it for this?

       case MEMBARRIER_CMD_GLOBAL:
               /* MEMBARRIER_CMD_GLOBAL is not compatible with nohz_full. */
               if (tick_nohz_full_enabled())
                       return -EINVAL;
               if (num_online_cpus() > 1)
                       synchronize_sched();
               return 0;
Quoted 1 line

Adding Mathieu as well who I believe is author/maintainer of membarrier.

See commit 907565337
"Fix: Disable sys_membarrier when nohz_full is enabled"

"Userspace applications should be allowed to expect the membarrier system call with MEMBARRIER_CMD_SHARED command to issue memory barriers on nohz_full CPUs, but synchronize_sched() does not take those into account."

So AFAIU you'd want to re-use membarrier to issue synchronize_sched, and you only care about kernel preempt off critical sections.

Clearly bpf code does not run in user-space, so it would "work".

But the guarantees provided by membarrier are not to synchronize against preempt off per se. It's just that the current implementation happens to do that. The point of membarrier is to turn user-space memory barriers into compiler barriers.

Quoted 5 lines

If what you need is to wait for a RCU grace period for whatever RCU flavor ebpf is using, I would against using membarrier for this. I would rather recommend adding a dedicated BPF_SYNCHRONIZE so you won't leak implementation details to user-space, *and* you can eventually change you RCU implementation for e.g. SRCU in the future if needed.

Quoted 11 lines

The point about future changes to underlying bpf mechanisms is valid. There is work already on the way to reduce the scope of preempt_off+rcu_lock that currently lasts the whole prog. We will have new prog types that won't have such wrappers and will do rcu_lock/unlock and preempt on/off only when necessary.
So something like BPF_SYNCHRONIZE will break soon, since the kernel cannot have guarantees on when programs finish. Calling this command BPF_SYNCHRONIZE_PROG also won't make sense for the same reason.
What we can do it instead is to define synchronization barrier for programs accessing maps. May be call it something like:
BPF_SYNC_MAP_ACCESS ?

Quoted 8 lines

I'm not sure what you're proposing. In the case the commit message describes, a user-space program that wants to "drain" a map needs to be confident that the map won't change under it, even across multiple bpf system calls on that map. One way of doing that is to ensure that nothing that could possibly hold a reference to that map is still running. Are you proposing some kind of refcount-draining approach? Simple locking won't work, since BPF programs can't block, and I don't see right now how a simple barrier would help.

Quoted 5 lines

I'm proposing few changes for your patch:
s/BPF_SYNCHRONIZE/BPF_SYNC_MAP_ACCESS/
and s/synchronize_sched/synchronize_rcu/
with detailed comment in uapi/bpf.h that has an example why folks would want to use this new cmd.

Quoted 8 lines

Thanks for clarifying.

Quoted 3 lines

I think the bpf maps will be rcu protected for foreseeable future even when rcu_read_lock/unlock will be done by the programs instead of kernel wrappers.

Can we guarantee that we always obtain a map reference and dispose of that reference inside the same critical section?

Quoted 9 lines

yep. the verifier will guarantee that.

Quoted 2 lines

If so, can BPF
programs then disable preemption for as long as they'd like?

you mean after the finish? no. only while running.
The verifier will match things like lookup/release, lock/unlock, preempt on/off and will make sure there is no dangling preempt disable after program returns.

Quoted 10 lines

It might be a silly question but does this new bpf program type provide a way to customize where to hold the rcu_lock/unlock and enable/disable preemption when creating the program? Since the BPF_SYNC_MAP_ACCESS cmd sounds not very useful if it only protect each single map access instead of the whole program. For example, sometimes we have to read the same map multiple times when running a eBPF program and if the userspace changed the map value between two reads, it may cause troubles. It would be great if we can have a RCU lock on a block of eBPF program and in that way I think BPF_SYNC_MAP_ACCESS cmd should be enough in handling userspace kernel racing problems.

we need to make sure we have detailed description of BPF_SYNC_MAP_ACCESS in uapi/bpf.h, since I feel the confusion regarding its usage is starting already. This new cmd will only make sense for map-in-map type of maps. Expecting that BPF_SYNC_MAP_ACCESS is somehow implies the end of
the program or doing some other map synchronization is not correct. Commit log of this patch got it right:
"""
For example, userspace can update a map->map entry to point to a new map, use BPF_SYNCHRONIZE to wait for any BPF programs using the old map to complete, and then drain the old map without fear that BPF programs may still be updating it.
"""
Now consider two cases:
1. single program does two lookups (inner and outer map) and the program is called twice (as trigger on two events or it was tail-called or through prog_array)
2. future single program without rcu wrappers does:
rcu_lock + outer lookup + inner lookup + rcu_unlock + .. some other bpf stuff .. + rcu_lock + outer lookup + inner lookup + rcu_unlock + .. yet another bpf stuff .. All of the above as part of single program that is called once.
These two cases are the same from the point of view of BPF_SYNC_MAP_ACCESS and completion of this cmd does not guarantee that program finished.
In both cases the user space will be correct to start draining the inner map it replaced after BPF_SYNC_MAP_ACCESS is done
(assuming that implementation does synchronize_rcu I mentioned earlier)

Preemption on/off as done today by prog wrappers or in the future explicitly by the prog code is orthogonal to this new cmd and map-in-map draining.

#16 lore raw
Quoted 13 lines · Alexei Starovoitov

we need to make sure we have detailed description of BPF_SYNC_MAP_ACCESS in uapi/bpf.h, since I feel the confusion regarding its usage is starting already. This new cmd will only make sense for map-in-map type of maps. Expecting that BPF_SYNC_MAP_ACCESS is somehow implies the end of
the program or doing some other map synchronization is not correct. Commit log of this patch got it right:
"""
For example, userspace can update a map->map entry to point to a new map, use BPF_SYNCHRONIZE to wait for any BPF programs using the old map to complete, and then drain the old map without fear that BPF programs may still be updating it.
"""

+1 for detailed documentation. For example, consider what happens if we have two map fds, one active and one standby, and a map-in-map with one element that contains a pointer to the currently-active map fd. The kernel program might do:

=====
const int current_map_key = 1;
void *current_map = bpf_map_lookup_elem(outer_map, &current_map_key);

int stats_key = 42;
uint64_t *stats_value = bpf_map_lookup_elem(current_map, &stats_key); __sync_fetch_and_add(&stats_value, 1);
=====

If a userspace does:

1. Write new fd to outer_map[1].
2. Call BPF_SYNC_MAP_ACCESS.
3. Start deleting everything in the old map.

How can we guarantee that the __sync_fetch_and_add will not add to the old map? If it does, we'll lose data. Will the verifier automatically hold the RCU lock for as long as a pointer to an inner map is valid?

#17 lore raw
Quoted 16 lines · Lorenzo Colitti
Quoted 13 lines · Alexei Starovoitov

we need to make sure we have detailed description of BPF_SYNC_MAP_ACCESS in uapi/bpf.h, since I feel the confusion regarding its usage is starting already. This new cmd will only make sense for map-in-map type of maps. Expecting that BPF_SYNC_MAP_ACCESS is somehow implies the end of
the program or doing some other map synchronization is not correct. Commit log of this patch got it right:
"""
For example, userspace can update a map->map entry to point to a new map, use BPF_SYNCHRONIZE to wait for any BPF programs using the old map to complete, and then drain the old map without fear that BPF programs may still be updating it.
"""

Quoted 3 lines

+1 for detailed documentation. For example, consider what happens if we have two map fds, one active and one standby, and a map-in-map with one element that contains a pointer to the currently-active map fd.

yes. that's exactly the use case that folks use.

Quoted 11 lines

The kernel program might do:

=====
const int current_map_key = 1;
void *current_map = bpf_map_lookup_elem(outer_map, &current_map_key);

int stats_key = 42;
uint64_t *stats_value = bpf_map_lookup_elem(current_map, &stats_key); __sync_fetch_and_add(&stats_value, 1);
=====

Quoted 8 lines

If a userspace does:

1. Write new fd to outer_map[1].
2. Call BPF_SYNC_MAP_ACCESS.
3. Start deleting everything in the old map.

How can we guarantee that the __sync_fetch_and_add will not add to the old map?

without any changes to the kernel sys_membarrier will work. And that's what folks use already.
BPF_SYNC_MAP_ACCESS implemented via synchronize_rcu() will work as well whether in the current implementation where rcu_lock/unlock is done outside of the program and in the future when rcu_lock/unlock are called by the program itself.

Quoted 2 lines

Will the verifier automatically
hold the RCU lock for as long as a pointer to an inner map is valid?

the verifier will guarantee the equivalency of future explicit lock/unlock by the program vs current situation of implicit lock/unlock by the kernel.
The verifier will track that bpf_map_lookup_elem() is done after rcu_lock and that the value returned by this helper is not accessed after rcu_unlock. Baby steps of dataflow analysis.

#18 lore raw

On Tue, Jul 10, 2018 at 08:40:19PM -0700, Alexei Starovoitov wrote:
[..]

Quoted 20 lines
Quoted 11 lines

The kernel program might do:

=====
const int current_map_key = 1;
void *current_map = bpf_map_lookup_elem(outer_map, &current_map_key);

int stats_key = 42;
uint64_t *stats_value = bpf_map_lookup_elem(current_map, &stats_key); __sync_fetch_and_add(&stats_value, 1);
=====

Quoted 8 lines

If a userspace does:

1. Write new fd to outer_map[1].
2. Call BPF_SYNC_MAP_ACCESS.
3. Start deleting everything in the old map.

How can we guarantee that the __sync_fetch_and_add will not add to the old map?

Quoted 6 lines

without any changes to the kernel sys_membarrier will work. And that's what folks use already.
BPF_SYNC_MAP_ACCESS implemented via synchronize_rcu() will work as well whether in the current implementation where rcu_lock/unlock is done outside of the program and in the future when rcu_lock/unlock are called by the program itself.

Cool Alexei and Lorenzo, sounds great to me. Daniel want to send a follow up patch with BPF_SYNC_MAP_ACCESS changes then?

Quoted 9 lines
Quoted 2 lines

Will the verifier automatically
hold the RCU lock for as long as a pointer to an inner map is valid?

the verifier will guarantee the equivalency of future explicit lock/unlock by the program vs current situation of implicit lock/unlock by the kernel.
The verifier will track that bpf_map_lookup_elem() is done after rcu_lock and that the value returned by this helper is not accessed after rcu_unlock. Baby steps of dataflow analysis.

Nice!

By the way just curious I was briefly going through kernel/bpf/arraymap.c. How are you protecting against load-store tearing of values of array map updates/lookups?

For example, if userspace reads an array map at a particular index, while another CPU is updating it, then userspace can read partial values / half-updated values right? Since rcu_read_lock is in use, I was hoping to find something like rcu_assign_pointer there to protect readers against concurrent updates. Thanks for any clarification.

Regards,

- Joel

#19 lore raw
Quoted 29 lines · Joel Fernandes

On Tue, Jul 10, 2018 at 08:40:19PM -0700, Alexei Starovoitov wrote:
[..]

Quoted 20 lines
Quoted 11 lines

The kernel program might do:

=====
const int current_map_key = 1;
void *current_map = bpf_map_lookup_elem(outer_map, &current_map_key);

int stats_key = 42;
uint64_t *stats_value = bpf_map_lookup_elem(current_map, &stats_key); __sync_fetch_and_add(&stats_value, 1);
=====

Quoted 8 lines

If a userspace does:

1. Write new fd to outer_map[1].
2. Call BPF_SYNC_MAP_ACCESS.
3. Start deleting everything in the old map.

How can we guarantee that the __sync_fetch_and_add will not add to the old map?

Quoted 6 lines

without any changes to the kernel sys_membarrier will work. And that's what folks use already.
BPF_SYNC_MAP_ACCESS implemented via synchronize_rcu() will work as well whether in the current implementation where rcu_lock/unlock is done outside of the program and in the future when rcu_lock/unlock are called by the program itself.

Quoted 2 lines

Cool Alexei and Lorenzo, sounds great to me. Daniel want to send a follow up patch with BPF_SYNC_MAP_ACCESS changes then?

Will do. Mind if I just mine this thread for the doc comment?

#20 lore raw
Quoted 30 lines · Daniel Colascione
Quoted 29 lines · Joel Fernandes

On Tue, Jul 10, 2018 at 08:40:19PM -0700, Alexei Starovoitov wrote:
[..]

Quoted 26 lines
Quoted 11 lines

The kernel program might do:

=====
const int current_map_key = 1;
void *current_map = bpf_map_lookup_elem(outer_map, &current_map_key);

int stats_key = 42;
uint64_t *stats_value = bpf_map_lookup_elem(current_map, &stats_key); __sync_fetch_and_add(&stats_value, 1);
=====

Quoted 8 lines

If a userspace does:

1. Write new fd to outer_map[1].
2. Call BPF_SYNC_MAP_ACCESS.
3. Start deleting everything in the old map.

How can we guarantee that the __sync_fetch_and_add will not add to the old map?

without any changes to the kernel sys_membarrier will work. And that's what folks use already.
BPF_SYNC_MAP_ACCESS implemented via synchronize_rcu() will work as well whether in the current implementation where rcu_lock/unlock is done outside of the program and in the future when rcu_lock/unlock are called by the program itself.

Quoted 4 lines
Quoted 2 lines

Cool Alexei and Lorenzo, sounds great to me. Daniel want to send a follow up patch with BPF_SYNC_MAP_ACCESS changes then?

Will do. Mind if I just mine this thread for the doc comment?

Do you mean the changelog? Yes I believe you could use the discussion in this thread for the rationale as Alexei described.

thanks,

- Joel

#21 lore raw

PATCH v2 1/1 Add BPF_SYNCHRONIZE_MAPS bpf(2) command

BPF_SYNCHRONIZE_MAPS waits for the release of any references to a BPF map made by a BPF program that is running at the time the BPF_SYNCHRONIZE_MAPS command is issued. The purpose of this command is to provide a means for userspace to replace a BPF map with another, newer version, then ensure that no component is still using the "old" map before manipulating the "old" map in some way.

---
 include/uapi/linux/bpf.h |  9 +++++++++
 kernel/bpf/syscall.c     | 13 +++++++++++++
 2 files changed, 22 insertions(+)
include/uapi/linux/bpf.h +9 -0
index b7db3261c62d..5b27e9117d3e 100644
@@ -75,6 +75,14 @@ struct bpf_lpm_trie_key {
 	__u8	data[0];	/* Arbitrary size */
 };
 
+/* BPF_SYNCHRONIZE_MAPS waits for the release of any references to a
+ * BPF map made by a BPF program that is running at the time the
+ * BPF_SYNCHRONIZE_MAPS command is issued. The purpose of this command
+ * is to provide a means for userspace to replace a BPF map with
+ * another, newer version, then ensure that no component is still
+ * using the "old" map before manipulating the "old" map in some way.
+ */
+
 /* BPF syscall commands, see bpf(2) man-page for details. */
 enum bpf_cmd {
 	BPF_MAP_CREATE,
@@ -98,6 +106,7 @@ enum bpf_cmd {
 	BPF_BTF_LOAD,
 	BPF_BTF_GET_FD_BY_ID,
 	BPF_TASK_FD_QUERY,
+	BPF_SYNCHRONIZE_MAPS,
 };
 
 enum bpf_map_type {
kernel/bpf/syscall.c +13 -0
index a31a1ba0f8ea..8bbd1a5d01d1 100644
@@ -2274,6 +2274,19 @@ SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *, uattr, unsigned int, siz
 	if (sysctl_unprivileged_bpf_disabled && !capable(CAP_SYS_ADMIN))
 		return -EPERM;
 
+	if (cmd == BPF_SYNCHRONIZE_MAPS) {
+		if (uattr != NULL || size != 0)
+			return -EINVAL;
+		err = security_bpf(cmd, NULL, 0);
+		if (err < 0)
+			return err;
+		/* BPF programs always enter a critical section while
+		 * they have a map reference outstanding.
+		 */
+		synchronize_rcu();
+		return 0;
+	}
+
 	err = bpf_check_uarg_tail_zero(uattr, sizeof(attr), size);
 	if (err)
 		return err;
Signature · 1 line
2.18.0.233.g985f88cf7e-goog
#22 lore raw
In reply to Joel Fernandes
Quoted 9 lines · Joel Fernandes

By the way just curious I was briefly going through kernel/bpf/arraymap.c. How are you protecting against load-store tearing of values of array map updates/lookups?

For example, if userspace reads an array map at a particular index, while another CPU is updating it, then userspace can read partial values / half-updated values right? Since rcu_read_lock is in use, I was hoping to find something like rcu_assign_pointer there to protect readers against concurrent updates. Thanks for any clarification.

I'm also curious about the answer to this question.

#23 lore raw
In reply to Mathieu Desnoyers
Quoted 80 lines · Mathieu Desnoyers
Quoted 73 lines · ----- On Jul 7, 2018, at 4:33 PM, Joel Fernandes [email protected] wrote:
Quoted 36 lines · Alexei Starovoitov
Quoted 35 lines · Daniel Colascione

BPF_SYNCHRONIZE waits for any BPF programs active at the time of BPF_SYNCHRONIZE to complete, allowing userspace to ensure atomicity of RCU data structure operations with respect to active programs. For example, userspace can update a map->map entry to point to a new map, use BPF_SYNCHRONIZE to wait for any BPF programs using the old map to complete, and then drain the old map without fear that BPF programs may still be updating it.

---
 include/uapi/linux/bpf.h |  1 +
 kernel/bpf/syscall.c     | 14 ++++++++++++++
 2 files changed, 15 insertions(+)
include/uapi/linux/bpf.h +1 -0
index b7db3261c62d..4365c50e8055 100644
@@ -98,6 +98,7 @@ enum bpf_cmd {
 	BPF_BTF_LOAD,
 	BPF_BTF_GET_FD_BY_ID,
 	BPF_TASK_FD_QUERY,
+	BPF_SYNCHRONIZE,
 };
 
 enum bpf_map_type {
kernel/bpf/syscall.c +0 -0
index d10ecd78105f..60ec7811846e 100644
@@ -2272,6 +2272,20 @@ SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *,

uattr, unsigned int, siz

kernel/bpf/syscall.c +0 -0
 	if (sysctl_unprivileged_bpf_disabled && !capable(CAP_SYS_ADMIN))
 		return -EPERM;
Quoted 15 lines

kernel/bpf/syscall.c SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *, uattr, unsigned int, siz · line 2286

Quoted 12 lines
kernel/bpf/syscall.c +12 -0
+	if (cmd == BPF_SYNCHRONIZE) {
+		if (uattr != NULL || size != 0)
+			return -EINVAL;
+		err = security_bpf(cmd, NULL, 0);
+		if (err < 0)
+			return err;
+		/* BPF programs are run with preempt disabled, so
+		 * synchronize_sched is sufficient even with
+		 * RCU_PREEMPT.
+		 */
+		synchronize_sched();
+		return 0;

I don't think it's necessary. sys_membarrier() can do this already and some folks use it exactly for this use case.

Alexei, the use of sys_membarrier for this purpose seems kind of weird to me though. No where does the manpage say membarrier should be implemented this way so what happens if the implementation changes?

Further, membarrier manpage says that a memory barrier should be matched with a matching barrier. In this use case there is no matching barrier, so it makes it weirder.

Lastly, sys_membarrier seems will not work on nohz-full systems, so its a bit fragile to depend on it for this?

       case MEMBARRIER_CMD_GLOBAL:
               /* MEMBARRIER_CMD_GLOBAL is not compatible with nohz_full. */
               if (tick_nohz_full_enabled())
                       return -EINVAL;
               if (num_online_cpus() > 1)
                       synchronize_sched();
               return 0;
Quoted 1 line

Adding Mathieu as well who I believe is author/maintainer of membarrier.

See commit 907565337
"Fix: Disable sys_membarrier when nohz_full is enabled"

Quoted 7 lines

"Userspace applications should be allowed to expect the membarrier system call with MEMBARRIER_CMD_SHARED command to issue memory barriers on nohz_full CPUs, but synchronize_sched() does not take those into account."

So AFAIU you'd want to re-use membarrier to issue synchronize_sched, and you only care about kernel preempt off critical sections.

Mathieu, Thanks a lot for your reply. I understand what you said and agree with you. Slight OT, but I tried to go back to first principles and understand how membarrier() uses synchronize_sched() for the "slow path" and it didn't make immediate sense to me. Let me clarify my dillema..

My understanding is membarrier's MEMBARRIER_CMD_GLOBAL will employ synchronize_sched to make sure all other CPUs aren't executing anymore in an section of usercode that happen to be accessing memory that was written to before the membarrier call was made. To do this, the system call will use synchronize_sched to try to guarantee that all user-mode execution that started before the membarrier call would be completed when the membarrier call returns. This guarantees that without using a real memory barrier on the "fast path", things work just fine and everyone wins.

But, going through RCU code, I see that a "RCU-sched quiecent state" on a CPU may be reached when the CPU receives a timer tick while executing in user mode:

void rcu_check_callbacks(int user)

{
	trace_rcu_utilization(TPS("Start scheduler-tick"));
	increment_cpu_stall_ticks();
	if (user || rcu_is_cpu_rrupt_from_idle()) {
[...]
		rcu_sched_qs();
		rcu_bh_qs();

The problem I see is the CPU could be executing usermode code at the time of the RCU sched-QS. This IMO is enough reason for synchronize_sched() to return, because the CPU in question just reported a QS (assuming all other CPUs also happen to do so if they needed to).

Then I am wondering how does the membarrier call even work, the tick could very well have interrupted the CPU while it was executing usermode code in the middle of a set of instructions performing memory accesses. Reporting a quiescent state at such an inopportune time would cause the membarrier call to prematurely return, no? Sorry if I missed something.

The other question I have is about the whole "nohz-full doesn't work" thing. I didn't fully understand why. RCU is already tracking the state of nohz-full CPUs because the rcu dynticks code in (kernel/rcu/tree.c) monitors transitions to and from usermode even if the timer tick is turned off. So why would it not work?

thanks a lot!

- Joel

#24 lore raw
Quoted 116 lines · Joel Fernandes
Quoted 80 lines · Mathieu Desnoyers
Quoted 73 lines · ----- On Jul 7, 2018, at 4:33 PM, Joel Fernandes [email protected] wrote:
Quoted 36 lines · Alexei Starovoitov
Quoted 35 lines · Daniel Colascione

BPF_SYNCHRONIZE waits for any BPF programs active at the time of BPF_SYNCHRONIZE to complete, allowing userspace to ensure atomicity of RCU data structure operations with respect to active programs. For example, userspace can update a map->map entry to point to a new map, use BPF_SYNCHRONIZE to wait for any BPF programs using the old map to complete, and then drain the old map without fear that BPF programs may still be updating it.

---
 include/uapi/linux/bpf.h |  1 +
 kernel/bpf/syscall.c     | 14 ++++++++++++++
 2 files changed, 15 insertions(+)
include/uapi/linux/bpf.h +1 -0
index b7db3261c62d..4365c50e8055 100644
@@ -98,6 +98,7 @@ enum bpf_cmd {
 	BPF_BTF_LOAD,
 	BPF_BTF_GET_FD_BY_ID,
 	BPF_TASK_FD_QUERY,
+	BPF_SYNCHRONIZE,
 };
 
 enum bpf_map_type {
kernel/bpf/syscall.c +0 -0
index d10ecd78105f..60ec7811846e 100644
@@ -2272,6 +2272,20 @@ SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *,

uattr, unsigned int, siz

kernel/bpf/syscall.c +0 -0
 	if (sysctl_unprivileged_bpf_disabled && !capable(CAP_SYS_ADMIN))
 		return -EPERM;
Quoted 15 lines

kernel/bpf/syscall.c SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *, uattr, unsigned int, siz · line 2286

Quoted 12 lines
kernel/bpf/syscall.c +12 -0
+	if (cmd == BPF_SYNCHRONIZE) {
+		if (uattr != NULL || size != 0)
+			return -EINVAL;
+		err = security_bpf(cmd, NULL, 0);
+		if (err < 0)
+			return err;
+		/* BPF programs are run with preempt disabled, so
+		 * synchronize_sched is sufficient even with
+		 * RCU_PREEMPT.
+		 */
+		synchronize_sched();
+		return 0;

I don't think it's necessary. sys_membarrier() can do this already and some folks use it exactly for this use case.

Alexei, the use of sys_membarrier for this purpose seems kind of weird to me though. No where does the manpage say membarrier should be implemented this way so what happens if the implementation changes?

Further, membarrier manpage says that a memory barrier should be matched with a matching barrier. In this use case there is no matching barrier, so it makes it weirder.

Lastly, sys_membarrier seems will not work on nohz-full systems, so its a bit fragile to depend on it for this?

       case MEMBARRIER_CMD_GLOBAL:
               /* MEMBARRIER_CMD_GLOBAL is not compatible with nohz_full. */
               if (tick_nohz_full_enabled())
                       return -EINVAL;
               if (num_online_cpus() > 1)
                       synchronize_sched();
               return 0;
Quoted 1 line

Adding Mathieu as well who I believe is author/maintainer of membarrier.

See commit 907565337
"Fix: Disable sys_membarrier when nohz_full is enabled"

Quoted 7 lines

"Userspace applications should be allowed to expect the membarrier system call with MEMBARRIER_CMD_SHARED command to issue memory barriers on nohz_full CPUs, but synchronize_sched() does not take those into account."

So AFAIU you'd want to re-use membarrier to issue synchronize_sched, and you only care about kernel preempt off critical sections.

Mathieu, Thanks a lot for your reply. I understand what you said and agree with you. Slight OT, but I tried to go back to first principles and understand how membarrier() uses synchronize_sched() for the "slow path" and it didn't make immediate sense to me. Let me clarify my dillema..

My understanding is membarrier's MEMBARRIER_CMD_GLOBAL will employ synchronize_sched to make sure all other CPUs aren't executing anymore in an section of usercode that happen to be accessing memory that was written to before the membarrier call was made. To do this, the system call will use synchronize_sched to try to guarantee that all user-mode execution that started before the membarrier call would be completed when the membarrier call returns. This guarantees that without using a real memory barrier on the "fast path", things work just fine and everyone wins.

But, going through RCU code, I see that a "RCU-sched quiecent state" on a CPU may be reached when the CPU receives a timer tick while executing in user mode:

void rcu_check_callbacks(int user)

{
	trace_rcu_utilization(TPS("Start scheduler-tick"));
	increment_cpu_stall_ticks();
	if (user || rcu_is_cpu_rrupt_from_idle()) {
[...]
		rcu_sched_qs();
		rcu_bh_qs();
Quoted 4 lines

The problem I see is the CPU could be executing usermode code at the time of the RCU sched-QS. This IMO is enough reason for synchronize_sched() to return, because the CPU in question just reported a QS (assuming all other CPUs also happen to do so if they needed to).

This scenario will have inserted the needed smp_mb() into the userspace instruction execution stream, as is required by the sys_membarrier use cases.

Quoted 5 lines

Then I am wondering how does the membarrier call even work, the tick could very well have interrupted the CPU while it was executing usermode code in the middle of a set of instructions performing memory accesses. Reporting a quiescent state at such an inopportune time would cause the membarrier call to prematurely return, no? Sorry if I missed something.

One way to think of sys_membarrier() is as something that promotes a barrier() to an smp_mb(). This barrier then separates the target CPU's accesses that the caller saw before the sys_membarrier() from that same CPU's accesses that the caller will see after the sys_membarrier().

Quoted 5 lines

The other question I have is about the whole "nohz-full doesn't work" thing. I didn't fully understand why. RCU is already tracking the state of nohz-full CPUs because the rcu dynticks code in (kernel/rcu/tree.c) monitors transitions to and from usermode even if the timer tick is turned off. So why would it not work?

In the nohz_full case, there is no need for sys_membarrier()'s call to synchronize_sched() to interact directly with the nohz_full CPU. It can instead look at the target CPU's dyntick-idle state, and that state would potentially have been set in the dim distant past, thus having no effect on the target CPU's current execution.

Thanx, Paul

#25 lore raw
Quoted 117 lines · Paul E. McKenney
Quoted 116 lines · Joel Fernandes
Quoted 80 lines · Mathieu Desnoyers
Quoted 73 lines · ----- On Jul 7, 2018, at 4:33 PM, Joel Fernandes [email protected] wrote:
Quoted 36 lines · Alexei Starovoitov
Quoted 35 lines · Daniel Colascione

BPF_SYNCHRONIZE waits for any BPF programs active at the time of BPF_SYNCHRONIZE to complete, allowing userspace to ensure atomicity of RCU data structure operations with respect to active programs. For example, userspace can update a map->map entry to point to a new map, use BPF_SYNCHRONIZE to wait for any BPF programs using the old map to complete, and then drain the old map without fear that BPF programs may still be updating it.

---
 include/uapi/linux/bpf.h |  1 +
 kernel/bpf/syscall.c     | 14 ++++++++++++++
 2 files changed, 15 insertions(+)
include/uapi/linux/bpf.h +1 -0
index b7db3261c62d..4365c50e8055 100644
@@ -98,6 +98,7 @@ enum bpf_cmd {
 	BPF_BTF_LOAD,
 	BPF_BTF_GET_FD_BY_ID,
 	BPF_TASK_FD_QUERY,
+	BPF_SYNCHRONIZE,
 };
 
 enum bpf_map_type {
kernel/bpf/syscall.c +0 -0
index d10ecd78105f..60ec7811846e 100644
@@ -2272,6 +2272,20 @@ SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *,

uattr, unsigned int, siz

kernel/bpf/syscall.c +0 -0
 	if (sysctl_unprivileged_bpf_disabled && !capable(CAP_SYS_ADMIN))
 		return -EPERM;
Quoted 15 lines

kernel/bpf/syscall.c SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *, uattr, unsigned int, siz · line 2286

Quoted 12 lines
kernel/bpf/syscall.c +12 -0
+	if (cmd == BPF_SYNCHRONIZE) {
+		if (uattr != NULL || size != 0)
+			return -EINVAL;
+		err = security_bpf(cmd, NULL, 0);
+		if (err < 0)
+			return err;
+		/* BPF programs are run with preempt disabled, so
+		 * synchronize_sched is sufficient even with
+		 * RCU_PREEMPT.
+		 */
+		synchronize_sched();
+		return 0;

I don't think it's necessary. sys_membarrier() can do this already and some folks use it exactly for this use case.

Alexei, the use of sys_membarrier for this purpose seems kind of weird to me though. No where does the manpage say membarrier should be implemented this way so what happens if the implementation changes?

Further, membarrier manpage says that a memory barrier should be matched with a matching barrier. In this use case there is no matching barrier, so it makes it weirder.

Lastly, sys_membarrier seems will not work on nohz-full systems, so its a bit fragile to depend on it for this?

       case MEMBARRIER_CMD_GLOBAL:
               /* MEMBARRIER_CMD_GLOBAL is not compatible with nohz_full. */
               if (tick_nohz_full_enabled())
                       return -EINVAL;
               if (num_online_cpus() > 1)
                       synchronize_sched();
               return 0;
Quoted 1 line

Adding Mathieu as well who I believe is author/maintainer of membarrier.

See commit 907565337
"Fix: Disable sys_membarrier when nohz_full is enabled"

Quoted 7 lines

"Userspace applications should be allowed to expect the membarrier system call with MEMBARRIER_CMD_SHARED command to issue memory barriers on nohz_full CPUs, but synchronize_sched() does not take those into account."

So AFAIU you'd want to re-use membarrier to issue synchronize_sched, and you only care about kernel preempt off critical sections.

Mathieu, Thanks a lot for your reply. I understand what you said and agree with you. Slight OT, but I tried to go back to first principles and understand how membarrier() uses synchronize_sched() for the "slow path" and it didn't make immediate sense to me. Let me clarify my dillema..

My understanding is membarrier's MEMBARRIER_CMD_GLOBAL will employ synchronize_sched to make sure all other CPUs aren't executing anymore in an section of usercode that happen to be accessing memory that was written to before the membarrier call was made. To do this, the system call will use synchronize_sched to try to guarantee that all user-mode execution that started before the membarrier call would be completed when the membarrier call returns. This guarantees that without using a real memory barrier on the "fast path", things work just fine and everyone wins.

But, going through RCU code, I see that a "RCU-sched quiecent state" on a CPU may be reached when the CPU receives a timer tick while executing in user mode:

void rcu_check_callbacks(int user)

{
	trace_rcu_utilization(TPS("Start scheduler-tick"));
	increment_cpu_stall_ticks();
	if (user || rcu_is_cpu_rrupt_from_idle()) {
[...]
		rcu_sched_qs();
		rcu_bh_qs();
Quoted 8 lines
Quoted 4 lines

The problem I see is the CPU could be executing usermode code at the time of the RCU sched-QS. This IMO is enough reason for synchronize_sched() to return, because the CPU in question just reported a QS (assuming all other CPUs also happen to do so if they needed to).

This scenario will have inserted the needed smp_mb() into the userspace instruction execution stream, as is required by the sys_membarrier use cases.

Oh ok, that makes sense!

Quoted 10 lines
Quoted 5 lines

Then I am wondering how does the membarrier call even work, the tick could very well have interrupted the CPU while it was executing usermode code in the middle of a set of instructions performing memory accesses. Reporting a quiescent state at such an inopportune time would cause the membarrier call to prematurely return, no? Sorry if I missed something.

One way to think of sys_membarrier() is as something that promotes a barrier() to an smp_mb(). This barrier then separates the target CPU's accesses that the caller saw before the sys_membarrier() from that same CPU's accesses that the caller will see after the sys_membarrier().

Got it!

Quoted 11 lines
Quoted 5 lines

The other question I have is about the whole "nohz-full doesn't work" thing. I didn't fully understand why. RCU is already tracking the state of nohz-full CPUs because the rcu dynticks code in (kernel/rcu/tree.c) monitors transitions to and from usermode even if the timer tick is turned off. So why would it not work?

In the nohz_full case, there is no need for sys_membarrier()'s call to synchronize_sched() to interact directly with the nohz_full CPU. It can instead look at the target CPU's dyntick-idle state, and that state would potentially have been set in the dim distant past, thus having no effect on the target CPU's current execution.

In nohz-idle case though, there's nothing to promote the barrier() to smp_mb() if you were to purely look at the dynticks-idle state on the nohz-full CPU executing in user mode?

So then it makes sense to me now that nohz-full needs something to IPI that CPU inorder to enforce the needed memory barrier and pure synchronize_sched() wouldn't work. So then makes me think the expedited versions of synchronize_sched should be able to do the job but I could off on a different track..

Thanks a lot,

-Joel

#26 lore raw
Quoted 158 lines · Joel Fernandes
Quoted 117 lines · Paul E. McKenney
Quoted 116 lines · Joel Fernandes
Quoted 80 lines · Mathieu Desnoyers
Quoted 73 lines · ----- On Jul 7, 2018, at 4:33 PM, Joel Fernandes [email protected] wrote:
Quoted 36 lines · Alexei Starovoitov
Quoted 35 lines · Daniel Colascione

BPF_SYNCHRONIZE waits for any BPF programs active at the time of BPF_SYNCHRONIZE to complete, allowing userspace to ensure atomicity of RCU data structure operations with respect to active programs. For example, userspace can update a map->map entry to point to a new map, use BPF_SYNCHRONIZE to wait for any BPF programs using the old map to complete, and then drain the old map without fear that BPF programs may still be updating it.

---
 include/uapi/linux/bpf.h |  1 +
 kernel/bpf/syscall.c     | 14 ++++++++++++++
 2 files changed, 15 insertions(+)
include/uapi/linux/bpf.h +1 -0
index b7db3261c62d..4365c50e8055 100644
@@ -98,6 +98,7 @@ enum bpf_cmd {
 	BPF_BTF_LOAD,
 	BPF_BTF_GET_FD_BY_ID,
 	BPF_TASK_FD_QUERY,
+	BPF_SYNCHRONIZE,
 };
 
 enum bpf_map_type {
kernel/bpf/syscall.c +0 -0
index d10ecd78105f..60ec7811846e 100644
@@ -2272,6 +2272,20 @@ SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *,

uattr, unsigned int, siz

kernel/bpf/syscall.c +0 -0
 	if (sysctl_unprivileged_bpf_disabled && !capable(CAP_SYS_ADMIN))
 		return -EPERM;
Quoted 15 lines

kernel/bpf/syscall.c

Quoted 12 lines
kernel/bpf/syscall.c +12 -0
+	if (cmd == BPF_SYNCHRONIZE) {
+		if (uattr != NULL || size != 0)
+			return -EINVAL;
+		err = security_bpf(cmd, NULL, 0);
+		if (err < 0)
+			return err;
+		/* BPF programs are run with preempt disabled, so
+		 * synchronize_sched is sufficient even with
+		 * RCU_PREEMPT.
+		 */
+		synchronize_sched();
+		return 0;

I don't think it's necessary. sys_membarrier() can do this already and some folks use it exactly for this use case.

Alexei, the use of sys_membarrier for this purpose seems kind of weird to me though. No where does the manpage say membarrier should be implemented this way so what happens if the implementation changes?

Further, membarrier manpage says that a memory barrier should be matched with a matching barrier. In this use case there is no matching barrier, so it makes it weirder.

Lastly, sys_membarrier seems will not work on nohz-full systems, so its a bit fragile to depend on it for this?

       case MEMBARRIER_CMD_GLOBAL:
               /* MEMBARRIER_CMD_GLOBAL is not compatible with nohz_full. */
               if (tick_nohz_full_enabled())
                       return -EINVAL;
               if (num_online_cpus() > 1)
                       synchronize_sched();
               return 0;
Quoted 1 line

Adding Mathieu as well who I believe is author/maintainer of membarrier.

See commit 907565337
"Fix: Disable sys_membarrier when nohz_full is enabled"

Quoted 7 lines

"Userspace applications should be allowed to expect the membarrier system call with MEMBARRIER_CMD_SHARED command to issue memory barriers on nohz_full CPUs, but synchronize_sched() does not take those into account."

So AFAIU you'd want to re-use membarrier to issue synchronize_sched, and you only care about kernel preempt off critical sections.

Mathieu, Thanks a lot for your reply. I understand what you said and agree with you. Slight OT, but I tried to go back to first principles and understand how membarrier() uses synchronize_sched() for the "slow path" and it didn't make immediate sense to me. Let me clarify my dillema..

My understanding is membarrier's MEMBARRIER_CMD_GLOBAL will employ synchronize_sched to make sure all other CPUs aren't executing anymore in an section of usercode that happen to be accessing memory that was written to before the membarrier call was made. To do this, the system call will use synchronize_sched to try to guarantee that all user-mode execution that started before the membarrier call would be completed when the membarrier call returns. This guarantees that without using a real memory barrier on the "fast path", things work just fine and everyone wins.

But, going through RCU code, I see that a "RCU-sched quiecent state" on a CPU may be reached when the CPU receives a timer tick while executing in user mode:

void rcu_check_callbacks(int user)

{
	trace_rcu_utilization(TPS("Start scheduler-tick"));
	increment_cpu_stall_ticks();
	if (user || rcu_is_cpu_rrupt_from_idle()) {
[...]
		rcu_sched_qs();
		rcu_bh_qs();
Quoted 8 lines
Quoted 4 lines

The problem I see is the CPU could be executing usermode code at the time of the RCU sched-QS. This IMO is enough reason for synchronize_sched() to return, because the CPU in question just reported a QS (assuming all other CPUs also happen to do so if they needed to).

This scenario will have inserted the needed smp_mb() into the userspace instruction execution stream, as is required by the sys_membarrier use cases.

Oh ok, that makes sense!

Quoted 10 lines
Quoted 5 lines

Then I am wondering how does the membarrier call even work, the tick could very well have interrupted the CPU while it was executing usermode code in the middle of a set of instructions performing memory accesses. Reporting a quiescent state at such an inopportune time would cause the membarrier call to prematurely return, no? Sorry if I missed something.

One way to think of sys_membarrier() is as something that promotes a barrier() to an smp_mb(). This barrier then separates the target CPU's accesses that the caller saw before the sys_membarrier() from that same CPU's accesses that the caller will see after the sys_membarrier().

Got it!

Quoted 11 lines
Quoted 5 lines

The other question I have is about the whole "nohz-full doesn't work" thing. I didn't fully understand why. RCU is already tracking the state of nohz-full CPUs because the rcu dynticks code in (kernel/rcu/tree.c) monitors transitions to and from usermode even if the timer tick is turned off. So why would it not work?

In the nohz_full case, there is no need for sys_membarrier()'s call to synchronize_sched() to interact directly with the nohz_full CPU. It can instead look at the target CPU's dyntick-idle state, and that state would potentially have been set in the dim distant past, thus having no effect on the target CPU's current execution.

In nohz-idle case though, there's nothing to promote the barrier() to smp_mb() if you were to purely look at the dynticks-idle state on the nohz-full CPU executing in user mode?

Quoted 5 lines

So then it makes sense to me now that nohz-full needs something to IPI that CPU inorder to enforce the needed memory barrier and pure synchronize_sched() wouldn't work. So then makes me think the expedited versions of synchronize_sched should be able to do the job but I could off on a different track..

The problem is that the expedited versions also check the dyntick-idle state and don't touch idle (or nohz_full usermode) CPUs. This is by design for the battery-powered embedded use case. ;-)

Thanx, Paul

Quoted 5 lines

Thanks a lot,

-Joel

#27 lore raw

On Tue, Jul 10, 2018 at 10:12:29AM -0700, Paul E. McKenney wrote:
[..]

Quoted 22 lines
Quoted 16 lines
Quoted 11 lines
Quoted 5 lines

The other question I have is about the whole "nohz-full doesn't work" thing. I didn't fully understand why. RCU is already tracking the state of nohz-full CPUs because the rcu dynticks code in (kernel/rcu/tree.c) monitors transitions to and from usermode even if the timer tick is turned off. So why would it not work?

In the nohz_full case, there is no need for sys_membarrier()'s call to synchronize_sched() to interact directly with the nohz_full CPU. It can instead look at the target CPU's dyntick-idle state, and that state would potentially have been set in the dim distant past, thus having no effect on the target CPU's current execution.

In nohz-idle case though, there's nothing to promote the barrier() to smp_mb() if you were to purely look at the dynticks-idle state on the nohz-full CPU executing in user mode?

Quoted 5 lines

So then it makes sense to me now that nohz-full needs something to IPI that CPU inorder to enforce the needed memory barrier and pure synchronize_sched() wouldn't work. So then makes me think the expedited versions of synchronize_sched should be able to do the job but I could off on a different track..

Quoted 3 lines

The problem is that the expedited versions also check the dyntick-idle state and don't touch idle (or nohz_full usermode) CPUs. This is by design for the battery-powered embedded use case. ;-)

Oh ok! ;)

I guess there's also a MEMBARRIER_CMD_GLOBAL_EXPEDITED which seems to IPI CPUs (I'm guessing regardless of dynticks state) and execute smp_mb within the IPI so userspace can fallback to using that incase MEMBARRIER_CMD_GLOBAL returns -EINVAL.

thanks!

-Joel

#28 lore raw
Quoted 28 lines · Joel Fernandes

On Tue, Jul 10, 2018 at 10:12:29AM -0700, Paul E. McKenney wrote:
[..]

Quoted 22 lines
Quoted 16 lines
Quoted 11 lines
Quoted 5 lines

The other question I have is about the whole "nohz-full doesn't work" thing. I didn't fully understand why. RCU is already tracking the state of nohz-full CPUs because the rcu dynticks code in (kernel/rcu/tree.c) monitors transitions to and from usermode even if the timer tick is turned off. So why would it not work?

In the nohz_full case, there is no need for sys_membarrier()'s call to synchronize_sched() to interact directly with the nohz_full CPU. It can instead look at the target CPU's dyntick-idle state, and that state would potentially have been set in the dim distant past, thus having no effect on the target CPU's current execution.

In nohz-idle case though, there's nothing to promote the barrier() to smp_mb() if you were to purely look at the dynticks-idle state on the nohz-full CPU executing in user mode?

Quoted 5 lines

So then it makes sense to me now that nohz-full needs something to IPI that CPU inorder to enforce the needed memory barrier and pure synchronize_sched() wouldn't work. So then makes me think the expedited versions of synchronize_sched should be able to do the job but I could off on a different track..

Quoted 3 lines

The problem is that the expedited versions also check the dyntick-idle state and don't touch idle (or nohz_full usermode) CPUs. This is by design for the battery-powered embedded use case. ;-)

Quoted 6 lines

Oh ok! ;)

I guess there's also a MEMBARRIER_CMD_GLOBAL_EXPEDITED which seems to IPI CPUs (I'm guessing regardless of dynticks state) and execute smp_mb within the IPI so userspace can fallback to using that incase MEMBARRIER_CMD_GLOBAL returns -EINVAL.

Yes, and this avoids IPIing idle CPUs via the ->mm checks. But it will IPI nohz_full CPUs in that same process, as it must for correctness.

Thanx, Paul