Re: [PATCH v3 bpf-next 11/19] bpf: keep track of total log content size in both fixed and rolling modes

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Tue, Apr 4, 2023 at 5:37 AM Andrii Nakryiko <andrii@xxxxxxxxxx> wrote:
>
> With these changes, it's now possible to do this sort of determination of
> log contents size in both BPF_LOG_FIXED and default rolling log mode.
> We need to keep in mind bpf_vlog_reset(), though, which shrinks log
> contents after successful verification of a particular code path. This
> log reset means that log->end_pos isn't always increasing, so to return
> back to users what should be the log buffer size to fit all log content
> without causing -ENOSPC even in the presenec of log resetting, we need

Just for you :) Nit: presence

> to keep maximum over "lifetime" of logging. We do this accounting in
> bpf_vlog_update_len_max() helper.

Ah, this is interesting! The way I conceived of this working is that
the kernel gives me the buffer size required to avoid truncation at
the final copy out _given the same flags_. From a user space POV I
don't care about the intermediate log that was truncated away, since I
in a way asked the kernel to not give me this information. Can we drop
the len_max logic and simply use end_pos?

> Another issue to keep in mind is that we limit log buffer size to 32-bit
> value and keep such log length as u32, but theoretically verifier could
> produce huge log stretching beyond 4GB. Instead of keeping (and later
> returning) 64-bit log length, we cap it at UINT_MAX. Current UAPI makes
> it impossible to specify log buffer size bigger than 4GB anyways, so we
> don't really loose anything here and keep everything consistently 32-bit
> in UAPI. This property will be utilized in next patch.
> These changes do incidentally fix one small issue with previous logging
> logic. Previously, if use provided log buffer of size N, and actual log
> output was exactly N-1 bytes + terminating \0, kernel logic coun't
> distinguish this condition from log truncation scenario which would end
> up with truncated log contents of N-1 bytes + terminating \0 as well.
>
> But now with log->end_pos being logical position that could go beyond
> actual log buffer size, we can distinguish these two conditions, which
> we do in this patch. This plays nicely with returning log_size_actual
> (implemented in UAPI in the next patch), as we can now guarantee that if
> user takes such log_size_actual and provides log buffer of that exact
> size, they will not get -ENOSPC in return.
>
> All in all, all these changes do conceptually unify fixed and rolling
> log modes much better, and allow a nice feature requested by users:
> knowing what should be the size of the buffer to avoid -ENOSPC.
>
> We'll plumb this through the UAPI and the code in the next patch.
>
> Signed-off-by: Andrii Nakryiko <andrii@xxxxxxxxxx>
> ---
>  include/linux/bpf_verifier.h | 12 ++-----
>  kernel/bpf/log.c             | 68 +++++++++++++++++++++++++-----------
>  2 files changed, 50 insertions(+), 30 deletions(-)
>
> diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
> index 4c926227f612..98d2eb382dbb 100644
> --- a/include/linux/bpf_verifier.h
> +++ b/include/linux/bpf_verifier.h
> @@ -504,6 +504,7 @@ struct bpf_verifier_log {
>         char __user *ubuf;
>         u32 level;
>         u32 len_total;
> +       u32 len_max;
>         char kbuf[BPF_VERIFIER_TMP_LOG_SIZE];
>  };
>
> @@ -517,23 +518,16 @@ struct bpf_verifier_log {
>  #define BPF_LOG_MIN_ALIGNMENT 8U
>  #define BPF_LOG_ALIGNMENT 40U
>
> -static inline u32 bpf_log_used(const struct bpf_verifier_log *log)
> -{
> -       return log->end_pos - log->start_pos;
> -}
> -
>  static inline bool bpf_verifier_log_full(const struct bpf_verifier_log *log)
>  {
>         if (log->level & BPF_LOG_FIXED)
> -               return bpf_log_used(log) >= log->len_total - 1;
> +               return log->end_pos >= log->len_total;
>         return false;
>  }
>
>  static inline bool bpf_verifier_log_needed(const struct bpf_verifier_log *log)
>  {
> -       return log &&
> -               ((log->level && log->ubuf && !bpf_verifier_log_full(log)) ||
> -                log->level == BPF_LOG_KERNEL);
> +       return log && log->level;
>  }
>
>  #define BPF_MAX_SUBPROGS 256
> diff --git a/kernel/bpf/log.c b/kernel/bpf/log.c
> index 14dc4d90adbe..acfe8f5d340a 100644
> --- a/kernel/bpf/log.c
> +++ b/kernel/bpf/log.c
> @@ -16,10 +16,26 @@ bool bpf_verifier_log_attr_valid(const struct bpf_verifier_log *log)
>                log->level && log->ubuf && !(log->level & ~BPF_LOG_MASK);
>  }
>
> +static void bpf_vlog_update_len_max(struct bpf_verifier_log *log, u32 add_len)
> +{
> +       /* add_len includes terminal \0, so no need for +1. */
> +       u64 len = log->end_pos + add_len;
> +
> +       /* log->len_max could be larger than our current len due to
> +        * bpf_vlog_reset() calls, so we maintain the max of any length at any
> +        * previous point
> +        */
> +       if (len > UINT_MAX)
> +               log->len_max = UINT_MAX;
> +       else if (len > log->len_max)
> +               log->len_max = len;
> +}
> +
>  void bpf_verifier_vlog(struct bpf_verifier_log *log, const char *fmt,
>                        va_list args)
>  {
> -       unsigned int n;
> +       u64 cur_pos;
> +       u32 new_n, n;
>
>         n = vscnprintf(log->kbuf, BPF_VERIFIER_TMP_LOG_SIZE, fmt, args);
>
> @@ -33,21 +49,28 @@ void bpf_verifier_vlog(struct bpf_verifier_log *log, const char *fmt,
>                 return;
>         }
>
> +       n += 1; /* include terminating zero */

So above we WARN_ONCE if n >= BPF_VERIFIER_TMP_LOG_SIZE - 1, but here
we add 1 anyways. Doesn't that mean we may read 1 byte past the end of
kbuf?


>         if (log->level & BPF_LOG_FIXED) {
> -               n = min(log->len_total - bpf_log_used(log) - 1, n);
> -               log->kbuf[n] = '\0';
> -               n += 1;
> -
> -               if (copy_to_user(log->ubuf + log->end_pos, log->kbuf, n))
> -                       goto fail;
> +               /* check if we have at least something to put into user buf */
> +               new_n = 0;
> +               if (log->end_pos < log->len_total - 1) {
> +                       new_n = min_t(u32, log->len_total - log->end_pos, n);
> +                       log->kbuf[new_n - 1] = '\0';
> +               }
>
> +               bpf_vlog_update_len_max(log, n);
> +               cur_pos = log->end_pos;
>                 log->end_pos += n - 1; /* don't count terminating '\0' */
> +
> +               if (log->ubuf && new_n &&
> +                   copy_to_user(log->ubuf + cur_pos, log->kbuf, new_n))
> +                       goto fail;
>         } else {
> -               u64 new_end, new_start, cur_pos;
> +               u64 new_end, new_start;
>                 u32 buf_start, buf_end, new_n;
>
> -               log->kbuf[n] = '\0';
> -               n += 1;
> +               log->kbuf[n - 1] = '\0';
> +               bpf_vlog_update_len_max(log, n);
>
>                 new_end = log->end_pos + n;
>                 if (new_end - log->start_pos >= log->len_total)
> @@ -66,6 +89,12 @@ void bpf_verifier_vlog(struct bpf_verifier_log *log, const char *fmt,
>                 if (buf_end == 0)
>                         buf_end = log->len_total;
>
> +               log->start_pos = new_start;
> +               log->end_pos = new_end - 1; /* don't count terminating '\0' */
> +
> +               if (!log->ubuf)
> +                       return;
> +
>                 /* if buf_start > buf_end, we wrapped around;
>                  * if buf_start == buf_end, then we fill ubuf completely; we
>                  * can't have buf_start == buf_end to mean that there is
> @@ -89,9 +118,6 @@ void bpf_verifier_vlog(struct bpf_verifier_log *log, const char *fmt,
>                                          buf_end))
>                                 goto fail;
>                 }
> -
> -               log->start_pos = new_start;
> -               log->end_pos = new_end - 1; /* don't count terminating '\0' */
>         }
>
>         return;
> @@ -114,8 +140,13 @@ void bpf_vlog_reset(struct bpf_verifier_log *log, u64 new_pos)
>         log->end_pos = new_pos;
>         if (log->end_pos < log->start_pos)
>                 log->start_pos = log->end_pos;
> -       div_u64_rem(new_pos, log->len_total, &pos);
> -       if (put_user(zero, log->ubuf + pos))
> +
> +       if (log->level & BPF_LOG_FIXED)
> +               pos = log->end_pos + 1;
> +       else
> +               div_u64_rem(new_pos, log->len_total, &pos);
> +
> +       if (log->ubuf && pos < log->len_total && put_user(zero, log->ubuf + pos))
>                 log->ubuf = NULL;
>  }
>
> @@ -167,12 +198,7 @@ static int bpf_vlog_reverse_ubuf(struct bpf_verifier_log *log, int start, int en
>
>  bool bpf_vlog_truncated(const struct bpf_verifier_log *log)
>  {
> -       if (!log->level)
> -               return false;
> -       else if (log->level & BPF_LOG_FIXED)
> -               return bpf_log_used(log) >= log->len_total - 1;
> -       else
> -               return log->start_pos > 0;
> +       return log->len_max > log->len_total;
>  }
>
>  void bpf_vlog_finalize(struct bpf_verifier_log *log)
> --
> 2.34.1
>




[Index of Archives]     [Linux Samsung SoC]     [Linux Rockchip SoC]     [Linux Actions SoC]     [Linux for Synopsys ARC Processors]     [Linux NFS]     [Linux NILFS]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]


  Powered by Linux