Received: by 2002:a05:6a10:9848:0:0:0:0 with SMTP id x8csp899126pxf; Thu, 8 Apr 2021 15:54:08 -0700 (PDT) X-Google-Smtp-Source: ABdhPJwLjN4UNh8bbmoKPmF2nYrdfqY36nwrjJiPl55nEZ69kT7A/FgdaBHqN+vqbWJjz3InEjJ1 X-Received: by 2002:a05:6402:430c:: with SMTP id m12mr14966191edc.138.1617922448435; Thu, 08 Apr 2021 15:54:08 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1617922448; cv=none; d=google.com; s=arc-20160816; b=BFgMG7e2D6yxqr/TTXT/WIjb/ckphH2p8PYg4dlKzb7M7YPoJJgGT452PIvTxdKzct A4aCvwxjXu0KtRYAAoLxo/AvBX7/OzrSJ/ugORG7WELINSkhmXSrXyC4ZEs1FBKJRfR8 xX5n7JOUefeh40sJNz4/2m+NV4ov1cWPLkg10ja9ACXMqa+dFMBLxeOdcrMK4J/4MY3e 2j1zG9/2HRUTEHrUoe8f1s8C93kYoilix5D98R65R7nHAnd63cE2rg6C6tX91V3FYorb is6/q6F6hh9dcKNISvG3Tby0udC5/aMkZkHLbDBPocqV3J1XuZ0T/hw725ig81NWPY12 ukkw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:cc:to:subject:message-id:date:from:in-reply-to :references:mime-version:dkim-signature; bh=EQ3h5xe8+jzukdzUQlxZOE8rskbjPlCZ9sPf83nXZbY=; b=HsfalENFuQWFN5ptIQJpxyaA5/K7eEgfkzexY5XqYZpDEmgbv8LfVEGmnFPblo3SaY 4NTdA7YFeInTHGpwqo+LE9zSP5V/UMDfrPZEahoIXogN3HI1mup/T6CN6UE+X56lg57h 03QdCtvV0idKuL0QpwO+C+O7/xaTQI2kQvBfXrxjZ0YB9DoKsxrmERPu7elAGGZHFLzF pHofGLMFZgVvMOiQ9TFAqwELJ9hLz4tDiNpZgMWKRd+EDnKDbLCDQ7eVbkM3cn52th/U pC+3tUlUXsesNXR8kmkIT+Yt+ZOyZ5X7uNio4WWTCfzqllqdK6IXgqijaOWveZWWYDJg YiwA== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@chromium.org header.s=google header.b=MksMSZzb; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=chromium.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [23.128.96.18]) by mx.google.com with ESMTP id f7si735828edu.503.2021.04.08.15.53.44; Thu, 08 Apr 2021 15:54:08 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) client-ip=23.128.96.18; Authentication-Results: mx.google.com; dkim=pass header.i=@chromium.org header.s=google header.b=MksMSZzb; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=chromium.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232697AbhDHWxG (ORCPT + 99 others); Thu, 8 Apr 2021 18:53:06 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:49592 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232643AbhDHWxF (ORCPT ); Thu, 8 Apr 2021 18:53:05 -0400 Received: from mail-il1-x134.google.com (mail-il1-x134.google.com [IPv6:2607:f8b0:4864:20::134]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 7663CC061761 for ; Thu, 8 Apr 2021 15:52:53 -0700 (PDT) Received: by mail-il1-x134.google.com with SMTP id b17so3190632ilh.6 for ; Thu, 08 Apr 2021 15:52:53 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; h=mime-version:references:in-reply-to:from:date:message-id:subject:to :cc; bh=EQ3h5xe8+jzukdzUQlxZOE8rskbjPlCZ9sPf83nXZbY=; b=MksMSZzbF0wh0wGQauFOyixOl0KDvpK82upbH62f/+ZzgHeGh5TON4vIVc6EIDrIC1 t8Y0dOJItALo2y2BKuAzhj6rufQi50ZiFkfErR+80UiqMdrZL3CN7bgKCL1RkRrtm+zc NC3PQH902awbjcNgZkezBJz0gpziuWp2xqXXE= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:mime-version:references:in-reply-to:from:date :message-id:subject:to:cc; bh=EQ3h5xe8+jzukdzUQlxZOE8rskbjPlCZ9sPf83nXZbY=; b=UzhBmCA5rqENcBw7/z8PvIk9Ff0PtIRXSBedwvgLGdA+ssfgG/sX6eUQ5YN/gfxld4 5AQ8AQQaoYR55fWsiiG4hWieZq6Kx5wP2FG3mTR2S7wHIWtBribhy7iLD7lXZR4VTHsT 4mw4NlM3ka4LroingQQaC9xduPtiEVRSahF5Cyyufgt5+wptKdhvwf7uWEQDCvYbZICl A2RAK3SdWtVon2NEFFOc5gnjIdWfHm4jixPBaqGIkMAjvd3O6/gxDVz3FW9A6e64MWTc PRIOR4sRlbPbNpUmrZKqf39EeSnc7Z3hw480gNpjzwDJqKmnY/a+sBZT63oKx80Y32H3 acxQ== X-Gm-Message-State: AOAM530pEbJc4B6JSyjM1/hQG+hwALYKxNifkr3lU6A0jpZKyXMppfnp HomEm0YgevV2OKxIqeKaf8vLa+UiyNQJPadnsHEJ+w== X-Received: by 2002:a05:6e02:5a2:: with SMTP id k2mr9099221ils.177.1617922372817; Thu, 08 Apr 2021 15:52:52 -0700 (PDT) MIME-Version: 1.0 References: <20210324022211.1718762-1-revest@chromium.org> <20210324022211.1718762-2-revest@chromium.org> In-Reply-To: From: Florent Revest Date: Fri, 9 Apr 2021 00:52:42 +0200 Message-ID: Subject: Re: [PATCH bpf-next v2 1/6] bpf: Factorize bpf_trace_printk and bpf_seq_printf To: Andrii Nakryiko Cc: bpf , Alexei Starovoitov , Daniel Borkmann , Andrii Nakryiko , Yonghong Song , KP Singh , Brendan Jackman , open list Content-Type: text/plain; charset="UTF-8" Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org On Wed, Apr 7, 2021 at 11:54 PM Andrii Nakryiko wrote: > On Tue, Apr 6, 2021 at 8:35 AM Florent Revest wrote: > > On Fri, Mar 26, 2021 at 11:51 PM Andrii Nakryiko > > wrote: > > > On Fri, Mar 26, 2021 at 2:53 PM Andrii Nakryiko > > > wrote: > > > > On Tue, Mar 23, 2021 at 7:23 PM Florent Revest wrote: > > > > > +/* Horrid workaround for getting va_list handling working with different > > > > > + * argument type combinations generically for 32 and 64 bit archs. > > > > > + */ > > > > > +#define BPF_CAST_FMT_ARG(arg_nb, args, mod) \ > > > > > + ((mod[arg_nb] == BPF_PRINTF_LONG_LONG || \ > > > > > + (mod[arg_nb] == BPF_PRINTF_LONG && __BITS_PER_LONG == 64)) \ > > > > > + ? args[arg_nb] \ > > > > > + : ((mod[arg_nb] == BPF_PRINTF_LONG || \ > > > > > + (mod[arg_nb] == BPF_PRINTF_INT && __BITS_PER_LONG == 32)) \ > > > > > > > > is this right? INT is always 32-bit, it's only LONG that differs. > > > > Shouldn't the rule be > > > > > > > > (LONG_LONG || LONG && __BITS_PER_LONG) -> (__u64)args[args_nb] > > > > (INT || LONG && __BITS_PER_LONG == 32) -> (__u32)args[args_nb] > > > > > > > > Does (long) cast do anything fancy when casting from u64? Sorry, maybe > > > > I'm confused. > > > > To be honest, I am also confused by that logic... :p My patch tries to > > conserve exactly the same logic as "88a5c690b6 bpf: fix > > bpf_trace_printk on 32 bit archs" because I was also afraid of missing > > something and could not test it on 32 bit arches. From that commit > > description, it is unclear to me what "u32 and long are passed > > differently to u64, since the result of C conditional operators > > follows the "usual arithmetic conversions" rules" means. Maybe Daniel > > can comment on this ? > > Yeah, no idea. Seems like the code above should work fine for 32 and > 64 bitness and both little- and big-endianness. Yeah, looks good to me as well. I'll use it in v3. > > > > > +int bpf_printf_preamble(char *fmt, u32 fmt_size, const u64 *raw_args, > > > > > + u64 *final_args, enum bpf_printf_mod_type *mod, > > > > > + u32 num_args) > > > > > +{ > > > > > + struct bpf_printf_buf *bufs = this_cpu_ptr(&bpf_printf_buf); > > > > > + int err, i, fmt_cnt = 0, copy_size, used; > > > > > + char *unsafe_ptr = NULL, *tmp_buf = NULL; > > > > > + bool prepare_args = final_args && mod; > > > > > > > > probably better to enforce that both or none are specified, otherwise > > > > return error > > > > Fair :) > > > > > it's actually three of them: raw_args, mod, and num_args, right? All > > > three are either NULL or non-NULL. > > > > It is a bit tricky to see from that patch but in "3/6 bpf: Add a > > bpf_snprintf helper" the verifier code calls this function with > > num_args != 0 to check whether the number of arguments is correct > > without actually converting anything. > > > > Also when the helper gets called, raw_args can come from the BPF > > program and be NULL but in that case we will also have num_args = 0 > > guaranteed by the helper so the loop will bail out if it encounters a > > format specifier. > > ok, but at least final_args and mod are locked together, so should be > enforced to be either null or not, right? Yes :) will do. > > > > > + enum bpf_printf_mod_type current_mod; > > > > > + size_t tmp_buf_len; > > > > > + u64 current_arg; > > > > > + char fmt_ptype; > > > > > + > > > > > + for (i = 0; i < fmt_size && fmt[i] != '\0'; i++) { > > > > > > > > Can we say that if the last character is not '\0' then it's a bad > > > > format string and return -EINVAL? And if \0 is inside the format > > > > string, then it's also a bad format string? I wonder what others think > > > > about this?... I think sanity should prevail. > > > > Overall, there are two situations: > > - bpf_seq_printf, bpf_trace_printk: we have a pointer and size but we > > are not guaranteed zero-termination > > - bpf_snprintf: we have a pointer, no size but it's guaranteed to be > > zero-terminated (by ARG_PTR_TO_CONST_STR) > > > > Currently, in the bpf_snprintf helper, I set fmt_size to UINT_MAX and > > the terminating condition will be fmt[i] == '\0'. > > As you pointed out a bit further, I got a bit carried away with the > > refactoring and dropped the zero-termination checks for the existing > > helpers ! > > > > So I see two possibilities: > > - either we check fmt[last] == '\0', add a bail out condition in the > > loop if we encounter another `\0` and set fmt_size to sprintf(fmt) in > > the bpf_snprintf verifier and helper code. > > - or we unconditionally call strnlen(fmt, fmt_size) in > > bpf_printf_preamble. If no 0 is found, we return an error, if there is > > one we treat it as the NULL terminating character. > > I was thinking about the second one. It is clearly acceptable on BPF > verifier side, though one might argue that we are doing extra work on > the BPF helper side. I don't think it matters in practice, so I'll be > fine with that, if that makes code cleaner and simpler. I also prefer that option, yes. > > > > > + if ((!isprint(fmt[i]) && !isspace(fmt[i])) || > > > > > + !isascii(fmt[i])) { > > > > > > > > && always binds tighter than ||, so you can omit extra (). I'd put > > > > this on a single line as well, but that's a total nit. > > > > Neat! :) > > I just got a compilation warning in a similar situation yesterday when > I dropped unnecessary parentheses, so some versions of compilers might > think it is not a good practice. Just keep that in mind. I don't think > I care enough. Yes, I noticed the same compilation warning and it bothers me. I'll keep the parentheses but make it one line. > > > > > + while (fmt[i] == '0' || fmt[i] == '+' || fmt[i] == '-' || > > > > > + fmt[i] == ' ') > > > > > + i++; > > > > > + if (fmt[i] >= '1' && fmt[i] <= '9') { > > > > > i++; > > > > > > > > Are we worried about integer overflow here? %123123123123123d > > > > hopefully won't crash anything, right? > > > > I expect that this should be handled gracefully by the subsequent call > > to snprintf(). Our parsing logic does not guarantee that the format > > string is 100% legit but it guarantees that it's safe to call > > vsnprintf with arguments coming from BPF. If the output buffer is too > > small to hold the output, the output will be truncated. > > > > Note that this is already how bpf_seq_printf already works. > > Ok, but let's not hope and add the test for this. Ok > > > > > - } else if (fmt[i] == 'p') { > > > > > - mod[fmt_cnt]++; > > > > > - if ((fmt[i + 1] == 'k' || > > > > > - fmt[i + 1] == 'u') && > > > > > + while (fmt[i] >= '0' && fmt[i] <= '9') > > > > > + i++; > > > > > > > > whoa, fmt_size shouldn't be ignored > > > > Oh no, I'll attach the stone of shame! It all made sense with > > bpf_snprintf() in mind because, there, we are guaranteed to have a > > NULL terminated string already but in an excess of refactoring > > enthusiasm I dropped the zero-termination check for the other helpers. > > > > But if we implement either of the options discussed above, then we do > > not need to constantly check fmt_size. > > let's see when we get to the next version ;) I don't remember code > enough by now, but I'll keep that in mind for the next revision > anyways Sure, I'll send v3 asap. > > > > > fmt_ptype = fmt[i + 1]; > > > > > i += 2; > > > > > goto fmt_str; > > > > > } > > > > > > > > > > - if (fmt[i + 1] == 'B') { > > > > > - i++; > > > > > + if (fmt[i + 1] == 0 || isspace(fmt[i + 1]) || > > > > > + ispunct(fmt[i + 1]) || fmt[i + 1] == 'K' || > > > > > + fmt[i + 1] == 'x' || fmt[i + 1] == 'B' || > > > > > + fmt[i + 1] == 's' || fmt[i + 1] == 'S') { > > > > > + /* just kernel pointers */ > > > > > + if (prepare_args) > > > > > + current_arg = raw_args[fmt_cnt]; > > > > > > > > fmt_cnt is not the best name, imo. arg_cnt makes more sense > > > > Mh, we already have "num_args" that can make it confusing. The way I see it: > > - the number of format specifiers is the number of %d %s... in the format string > > - the number of arguments is the number of values given in the raw_args array. > > > > Well, if you read "fmt_cnt" as "number of formatters" then yeah, I > suppose it's fine. Never mind. Just fmt_cnt and fmt_size refers to > slightly different "fmt"s, which confused me for a bit, but that's ok. > You use different naming conventions, which is inconsistent, so maybe > adjust that for purists (i.e., if you have num_args, then you should > have num_fmts; or, alternatively, arg_cnt and fmt_cnt). But I'm just > nitpicking, obviously. I agree, I'll see if I can clean this up a bit. > > > > > + if (data_len & 7 || data_len > MAX_SEQ_PRINTF_VARARGS * 8 || > > > > > + (data_len && !data)) > > > > > > > > data && !data_len is also an error, no? > > > > Isn't that checked by the verifier ? > > data_len is ARG_CONST_SIZE_OR_ZERO, so data_len == 0 is allowed by > verifier. But it's probably no harm either to allow data != NULL and > data_len = 0. Might simplify some more dynamic use of snprintf(), > actually. Agree