Line data Source code
1 : /* SPDX-License-Identifier: GPL-2.0-only */
2 : /*
3 : * Copyright (c) 2023 Meta Platforms, Inc. and affiliates.
4 : */
5 :
6 : #include "bpfilter/cgen/swich.h"
7 :
8 : #include <linux/bpf.h>
9 : #include <linux/bpf_common.h>
10 :
11 : #include <errno.h>
12 : #include <stddef.h>
13 : #include <stdint.h>
14 : #include <stdlib.h>
15 : #include <string.h>
16 :
17 : #include "bpfilter/cgen/jmp.h"
18 : #include "bpfilter/cgen/program.h"
19 : #include "core/helper.h"
20 : #include "core/list.h"
21 : #include "core/logger.h"
22 :
23 : #include "external/filter.h"
24 :
25 : /// Cleanup attribute for a @ref bf_swich_option variable.
26 : #define _cleanup_bf_swich_option_ \
27 : __attribute__((cleanup(_bf_swich_option_free)))
28 :
29 : /**
30 : * @struct bf_swich_option
31 : *
32 : * Represent a @c case of the switch: contains the instructions to execute if
33 : * the switch's register is equal to a specific value.
34 : */
35 : struct bf_swich_option
36 : {
37 : /// Immediate value to match against the switch's register.
38 : uint32_t imm;
39 : /// Jump context used to jump from the comparison to the bytecode to
40 : /// execute, then to the end of the switch.
41 : struct bf_jmpctx jmp;
42 : /// Number of instructions for this option.
43 : size_t insns_len;
44 : /// Instructions to execute, as a flexible array member.
45 : struct bpf_insn insns[];
46 : };
47 :
48 : static void _bf_swich_option_free(struct bf_swich_option **option);
49 :
50 : /**
51 : * Allocate and initialize a new switch option (case).
52 : *
53 : * @param option Switch option to allocate and initialize. Can't be NULL.
54 : * @param imm Immediate value to match against the switch's register.
55 : * @param insns Instructions to execute if the option matches.
56 : * @param insns_len Number of instructions in @p insns .
57 : * @return 0 on success, or negative errno value on failure.
58 : */
59 13 : static int _bf_swich_option_new(struct bf_swich_option **option, uint32_t imm,
60 : const struct bpf_insn *insns, size_t insns_len)
61 : {
62 11 : _cleanup_bf_swich_option_ struct bf_swich_option *_option = NULL;
63 :
64 13 : bf_assert(option);
65 12 : bf_assert(insns);
66 :
67 11 : _option = malloc(sizeof(*_option) + (sizeof(struct bpf_insn) * insns_len));
68 11 : if (!_option)
69 : return -ENOMEM;
70 :
71 11 : _option->imm = imm;
72 11 : _option->insns_len = insns_len;
73 11 : memcpy(_option->insns, insns, sizeof(struct bpf_insn) * insns_len);
74 :
75 11 : *option = TAKE_PTR(_option);
76 :
77 11 : return 0;
78 : }
79 :
80 : /**
81 : * Free a switch option.
82 : *
83 : * Free @p option is it points to valid memory. If @p option points to a NULL
84 : * pointer, nothing is done.
85 : *
86 : * @param option Option to free. Can't be NULL.
87 : */
88 36 : static void _bf_swich_option_free(struct bf_swich_option **option)
89 : {
90 36 : bf_assert(option);
91 :
92 35 : if (!*option)
93 : return;
94 :
95 11 : free(*option);
96 11 : *option = NULL;
97 : }
98 :
99 5 : int bf_swich_init(struct bf_swich *swich, struct bf_program *program, int reg)
100 : {
101 5 : bf_assert(swich);
102 4 : bf_assert(program);
103 :
104 3 : swich->program = program;
105 3 : swich->reg = reg;
106 :
107 3 : bf_list_init(
108 : &swich->options,
109 3 : (bf_list_ops[]) {{.free = (bf_list_ops_free)_bf_swich_option_free}});
110 :
111 3 : swich->default_opt = NULL;
112 :
113 3 : return 0;
114 : }
115 :
116 7 : void bf_swich_cleanup(struct bf_swich *swich)
117 : {
118 7 : bf_assert(swich);
119 :
120 6 : bf_list_clean(&swich->options);
121 6 : _bf_swich_option_free(&swich->default_opt);
122 6 : free(swich->default_opt);
123 6 : }
124 :
125 6 : int bf_swich_add_option(struct bf_swich *swich, uint32_t imm,
126 : const struct bpf_insn *insns, size_t insns_len)
127 : {
128 6 : _cleanup_bf_swich_option_ struct bf_swich_option *option = NULL;
129 : int r;
130 :
131 6 : bf_assert(swich);
132 6 : bf_assert(insns);
133 :
134 6 : r = _bf_swich_option_new(&option, imm, insns, insns_len);
135 6 : if (r)
136 : return r;
137 :
138 6 : r = bf_list_add_tail(&swich->options, option);
139 6 : if (r)
140 : return r;
141 :
142 6 : TAKE_PTR(option);
143 :
144 6 : return 0;
145 : }
146 :
147 2 : int bf_swich_set_default(struct bf_swich *swich, const struct bpf_insn *insns,
148 : size_t insns_len)
149 : {
150 2 : _cleanup_bf_swich_option_ struct bf_swich_option *option = NULL;
151 : int r;
152 :
153 2 : bf_assert(swich);
154 2 : bf_assert(insns);
155 :
156 2 : if (swich->default_opt) {
157 1 : bf_warn("default bf_swich option already exists, replacing it");
158 1 : _bf_swich_option_free(&swich->default_opt);
159 : }
160 :
161 2 : r = _bf_swich_option_new(&option, 0, insns, insns_len);
162 2 : if (r)
163 : return r;
164 :
165 2 : swich->default_opt = TAKE_PTR(option);
166 :
167 2 : return 0;
168 : }
169 :
170 2 : int bf_swich_generate(struct bf_swich *swich)
171 : {
172 2 : struct bf_program *program = swich->program;
173 :
174 : // Match an option against the value and jump to the related bytecode
175 14 : bf_list_foreach (&swich->options, option_node) {
176 6 : struct bf_swich_option *option = bf_list_node_get_data(option_node);
177 :
178 6 : option->jmp = bf_jmpctx_get(
179 : program, BPF_JMP_IMM(BPF_JEQ, swich->reg, option->imm, 0));
180 : }
181 :
182 : // Insert the default option if any
183 2 : if (swich->default_opt)
184 1 : swich->default_opt->jmp = bf_jmpctx_get(program, BPF_JMP_A(0));
185 :
186 : // Insert each option's bytecode
187 14 : bf_list_foreach (&swich->options, option_node) {
188 6 : struct bf_swich_option *option = bf_list_node_get_data(option_node);
189 :
190 6 : bf_jmpctx_cleanup(&option->jmp);
191 18 : for (size_t i = 0; i < option->insns_len; ++i)
192 12 : EMIT(program, option->insns[i]);
193 6 : option->jmp = bf_jmpctx_get(program, BPF_JMP_A(0));
194 : }
195 :
196 : // Insert the default instructions
197 2 : if (swich->default_opt) {
198 1 : bf_jmpctx_cleanup(&swich->default_opt->jmp);
199 4 : for (size_t i = 0; i < swich->default_opt->insns_len; ++i)
200 3 : EMIT(program, swich->default_opt->insns[i]);
201 : }
202 :
203 16 : bf_list_foreach (&swich->options, option_node) {
204 6 : struct bf_swich_option *option = bf_list_node_get_data(option_node);
205 :
206 6 : bf_jmpctx_cleanup(&option->jmp);
207 : }
208 :
209 : return 0;
210 : }
|