Skip to content

Commit d9c9e4d

Browse files
FlorentRevestAlexei Starovoitov
authored andcommitted
bpf: Factorize bpf_trace_printk and bpf_seq_printf
Two helpers (trace_printk and seq_printf) have very similar implementations of format string parsing and a third one is coming (snprintf). To avoid code duplication and make the code easier to maintain, this moves the operations associated with format string parsing (validation and argument sanitization) into one generic function. The implementation of the two existing helpers already drifted quite a bit so unifying them entailed a lot of changes: - bpf_trace_printk always expected fmt[fmt_size] to be the terminating NULL character, this is no longer true, the first 0 is terminating. - bpf_trace_printk now supports %% (which produces the percentage char). - bpf_trace_printk now skips width formating fields. - bpf_trace_printk now supports the X modifier (capital hexadecimal). - bpf_trace_printk now supports %pK, %px, %pB, %pi4, %pI4, %pi6 and %pI6 - argument casting on 32 bit has been simplified into one macro and using an enum instead of obscure int increments. - bpf_seq_printf now uses bpf_trace_copy_string instead of strncpy_from_kernel_nofault and handles the %pks %pus specifiers. - bpf_seq_printf now prints longs correctly on 32 bit architectures. - both were changed to use a global per-cpu tmp buffer instead of one stack buffer for trace_printk and 6 small buffers for seq_printf. - to avoid per-cpu buffer usage conflict, these helpers disable preemption while the per-cpu buffer is in use. - both helpers now support the %ps and %pS specifiers to print symbols. The implementation is also moved from bpf_trace.c to helpers.c because the upcoming bpf_snprintf helper will be made available to all BPF programs and will need it. Signed-off-by: Florent Revest <revest@chromium.org> Signed-off-by: Alexei Starovoitov <ast@kernel.org> Link: https://lore.kernel.org/bpf/20210419155243.1632274-2-revest@chromium.org
1 parent cdf0e80 commit d9c9e4d

File tree

3 files changed

+313
-334
lines changed

3 files changed

+313
-334
lines changed

include/linux/bpf.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2077,4 +2077,24 @@ int bpf_arch_text_poke(void *ip, enum bpf_text_poke_type t,
20772077
struct btf_id_set;
20782078
bool btf_id_set_contains(const struct btf_id_set *set, u32 id);
20792079

2080+
enum bpf_printf_mod_type {
2081+
BPF_PRINTF_INT,
2082+
BPF_PRINTF_LONG,
2083+
BPF_PRINTF_LONG_LONG,
2084+
};
2085+
2086+
/* Workaround for getting va_list handling working with different argument type
2087+
* combinations generically for 32 and 64 bit archs.
2088+
*/
2089+
#define BPF_CAST_FMT_ARG(arg_nb, args, mod) \
2090+
(mod[arg_nb] == BPF_PRINTF_LONG_LONG || \
2091+
(mod[arg_nb] == BPF_PRINTF_LONG && __BITS_PER_LONG == 64) \
2092+
? (u64)args[arg_nb] \
2093+
: (u32)args[arg_nb])
2094+
2095+
int bpf_printf_prepare(char *fmt, u32 fmt_size, const u64 *raw_args,
2096+
u64 *final_args, enum bpf_printf_mod_type *mod,
2097+
u32 num_args);
2098+
void bpf_printf_cleanup(void);
2099+
20802100
#endif /* _LINUX_BPF_H */

kernel/bpf/helpers.c

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -669,6 +669,262 @@ const struct bpf_func_proto bpf_this_cpu_ptr_proto = {
669669
.arg1_type = ARG_PTR_TO_PERCPU_BTF_ID,
670670
};
671671

672+
static int bpf_trace_copy_string(char *buf, void *unsafe_ptr, char fmt_ptype,
673+
size_t bufsz)
674+
{
675+
void __user *user_ptr = (__force void __user *)unsafe_ptr;
676+
677+
buf[0] = 0;
678+
679+
switch (fmt_ptype) {
680+
case 's':
681+
#ifdef CONFIG_ARCH_HAS_NON_OVERLAPPING_ADDRESS_SPACE
682+
if ((unsigned long)unsafe_ptr < TASK_SIZE)
683+
return strncpy_from_user_nofault(buf, user_ptr, bufsz);
684+
fallthrough;
685+
#endif
686+
case 'k':
687+
return strncpy_from_kernel_nofault(buf, unsafe_ptr, bufsz);
688+
case 'u':
689+
return strncpy_from_user_nofault(buf, user_ptr, bufsz);
690+
}
691+
692+
return -EINVAL;
693+
}
694+
695+
/* Per-cpu temp buffers which can be used by printf-like helpers for %s or %p
696+
*/
697+
#define MAX_PRINTF_BUF_LEN 512
698+
699+
struct bpf_printf_buf {
700+
char tmp_buf[MAX_PRINTF_BUF_LEN];
701+
};
702+
static DEFINE_PER_CPU(struct bpf_printf_buf, bpf_printf_buf);
703+
static DEFINE_PER_CPU(int, bpf_printf_buf_used);
704+
705+
static int try_get_fmt_tmp_buf(char **tmp_buf)
706+
{
707+
struct bpf_printf_buf *bufs;
708+
int used;
709+
710+
if (*tmp_buf)
711+
return 0;
712+
713+
preempt_disable();
714+
used = this_cpu_inc_return(bpf_printf_buf_used);
715+
if (WARN_ON_ONCE(used > 1)) {
716+
this_cpu_dec(bpf_printf_buf_used);
717+
preempt_enable();
718+
return -EBUSY;
719+
}
720+
bufs = this_cpu_ptr(&bpf_printf_buf);
721+
*tmp_buf = bufs->tmp_buf;
722+
723+
return 0;
724+
}
725+
726+
void bpf_printf_cleanup(void)
727+
{
728+
if (this_cpu_read(bpf_printf_buf_used)) {
729+
this_cpu_dec(bpf_printf_buf_used);
730+
preempt_enable();
731+
}
732+
}
733+
734+
/*
735+
* bpf_parse_fmt_str - Generic pass on format strings for printf-like helpers
736+
*
737+
* Returns a negative value if fmt is an invalid format string or 0 otherwise.
738+
*
739+
* This can be used in two ways:
740+
* - Format string verification only: when final_args and mod are NULL
741+
* - Arguments preparation: in addition to the above verification, it writes in
742+
* final_args a copy of raw_args where pointers from BPF have been sanitized
743+
* into pointers safe to use by snprintf. This also writes in the mod array
744+
* the size requirement of each argument, usable by BPF_CAST_FMT_ARG for ex.
745+
*
746+
* In argument preparation mode, if 0 is returned, safe temporary buffers are
747+
* allocated and bpf_printf_cleanup should be called to free them after use.
748+
*/
749+
int bpf_printf_prepare(char *fmt, u32 fmt_size, const u64 *raw_args,
750+
u64 *final_args, enum bpf_printf_mod_type *mod,
751+
u32 num_args)
752+
{
753+
char *unsafe_ptr = NULL, *tmp_buf = NULL, *fmt_end;
754+
size_t tmp_buf_len = MAX_PRINTF_BUF_LEN;
755+
int err, i, num_spec = 0, copy_size;
756+
enum bpf_printf_mod_type cur_mod;
757+
u64 cur_arg;
758+
char fmt_ptype;
759+
760+
if (!!final_args != !!mod)
761+
return -EINVAL;
762+
763+
fmt_end = strnchr(fmt, fmt_size, 0);
764+
if (!fmt_end)
765+
return -EINVAL;
766+
fmt_size = fmt_end - fmt;
767+
768+
for (i = 0; i < fmt_size; i++) {
769+
if ((!isprint(fmt[i]) && !isspace(fmt[i])) || !isascii(fmt[i])) {
770+
err = -EINVAL;
771+
goto cleanup;
772+
}
773+
774+
if (fmt[i] != '%')
775+
continue;
776+
777+
if (fmt[i + 1] == '%') {
778+
i++;
779+
continue;
780+
}
781+
782+
if (num_spec >= num_args) {
783+
err = -EINVAL;
784+
goto cleanup;
785+
}
786+
787+
/* The string is zero-terminated so if fmt[i] != 0, we can
788+
* always access fmt[i + 1], in the worst case it will be a 0
789+
*/
790+
i++;
791+
792+
/* skip optional "[0 +-][num]" width formatting field */
793+
while (fmt[i] == '0' || fmt[i] == '+' || fmt[i] == '-' ||
794+
fmt[i] == ' ')
795+
i++;
796+
if (fmt[i] >= '1' && fmt[i] <= '9') {
797+
i++;
798+
while (fmt[i] >= '0' && fmt[i] <= '9')
799+
i++;
800+
}
801+
802+
if (fmt[i] == 'p') {
803+
cur_mod = BPF_PRINTF_LONG;
804+
805+
if ((fmt[i + 1] == 'k' || fmt[i + 1] == 'u') &&
806+
fmt[i + 2] == 's') {
807+
fmt_ptype = fmt[i + 1];
808+
i += 2;
809+
goto fmt_str;
810+
}
811+
812+
if (fmt[i + 1] == 0 || isspace(fmt[i + 1]) ||
813+
ispunct(fmt[i + 1]) || fmt[i + 1] == 'K' ||
814+
fmt[i + 1] == 'x' || fmt[i + 1] == 'B' ||
815+
fmt[i + 1] == 's' || fmt[i + 1] == 'S') {
816+
/* just kernel pointers */
817+
if (final_args)
818+
cur_arg = raw_args[num_spec];
819+
goto fmt_next;
820+
}
821+
822+
/* only support "%pI4", "%pi4", "%pI6" and "%pi6". */
823+
if ((fmt[i + 1] != 'i' && fmt[i + 1] != 'I') ||
824+
(fmt[i + 2] != '4' && fmt[i + 2] != '6')) {
825+
err = -EINVAL;
826+
goto cleanup;
827+
}
828+
829+
i += 2;
830+
if (!final_args)
831+
goto fmt_next;
832+
833+
if (try_get_fmt_tmp_buf(&tmp_buf)) {
834+
err = -EBUSY;
835+
goto out;
836+
}
837+
838+
copy_size = (fmt[i + 2] == '4') ? 4 : 16;
839+
if (tmp_buf_len < copy_size) {
840+
err = -ENOSPC;
841+
goto cleanup;
842+
}
843+
844+
unsafe_ptr = (char *)(long)raw_args[num_spec];
845+
err = copy_from_kernel_nofault(tmp_buf, unsafe_ptr,
846+
copy_size);
847+
if (err < 0)
848+
memset(tmp_buf, 0, copy_size);
849+
cur_arg = (u64)(long)tmp_buf;
850+
tmp_buf += copy_size;
851+
tmp_buf_len -= copy_size;
852+
853+
goto fmt_next;
854+
} else if (fmt[i] == 's') {
855+
cur_mod = BPF_PRINTF_LONG;
856+
fmt_ptype = fmt[i];
857+
fmt_str:
858+
if (fmt[i + 1] != 0 &&
859+
!isspace(fmt[i + 1]) &&
860+
!ispunct(fmt[i + 1])) {
861+
err = -EINVAL;
862+
goto cleanup;
863+
}
864+
865+
if (!final_args)
866+
goto fmt_next;
867+
868+
if (try_get_fmt_tmp_buf(&tmp_buf)) {
869+
err = -EBUSY;
870+
goto out;
871+
}
872+
873+
if (!tmp_buf_len) {
874+
err = -ENOSPC;
875+
goto cleanup;
876+
}
877+
878+
unsafe_ptr = (char *)(long)raw_args[num_spec];
879+
err = bpf_trace_copy_string(tmp_buf, unsafe_ptr,
880+
fmt_ptype, tmp_buf_len);
881+
if (err < 0) {
882+
tmp_buf[0] = '\0';
883+
err = 1;
884+
}
885+
886+
cur_arg = (u64)(long)tmp_buf;
887+
tmp_buf += err;
888+
tmp_buf_len -= err;
889+
890+
goto fmt_next;
891+
}
892+
893+
cur_mod = BPF_PRINTF_INT;
894+
895+
if (fmt[i] == 'l') {
896+
cur_mod = BPF_PRINTF_LONG;
897+
i++;
898+
}
899+
if (fmt[i] == 'l') {
900+
cur_mod = BPF_PRINTF_LONG_LONG;
901+
i++;
902+
}
903+
904+
if (fmt[i] != 'i' && fmt[i] != 'd' && fmt[i] != 'u' &&
905+
fmt[i] != 'x' && fmt[i] != 'X') {
906+
err = -EINVAL;
907+
goto cleanup;
908+
}
909+
910+
if (final_args)
911+
cur_arg = raw_args[num_spec];
912+
fmt_next:
913+
if (final_args) {
914+
mod[num_spec] = cur_mod;
915+
final_args[num_spec] = cur_arg;
916+
}
917+
num_spec++;
918+
}
919+
920+
err = 0;
921+
cleanup:
922+
if (err)
923+
bpf_printf_cleanup();
924+
out:
925+
return err;
926+
}
927+
672928
const struct bpf_func_proto bpf_get_current_task_proto __weak;
673929
const struct bpf_func_proto bpf_probe_read_user_proto __weak;
674930
const struct bpf_func_proto bpf_probe_read_user_str_proto __weak;

0 commit comments

Comments
 (0)