Skip to content

Commit fefb10e

Browse files
Xu KuohaiKernel Patches Daemon
Xu Kuohai
authored and
Kernel Patches Daemon
committed
bpf, arm64: bpf trampoline for arm64
Add bpf trampoline support for arm64. Most of the logic is the same as x86. fentry before bpf trampoline hooked: mov x9, x30 nop fentry after bpf trampoline hooked: mov x9, x30 bl <bpf_trampoline> Tested on qemu, result: #55 fentry_fexit:OK #56 fentry_test:OK #58 fexit_sleep:OK #59 fexit_stress:OK #60 fexit_test:OK #67 get_func_args_test:OK #68 get_func_ip_test:OK #101 modify_return:OK Signed-off-by: Xu Kuohai <xukuohai@huawei.com>
1 parent c388541 commit fefb10e

File tree

2 files changed

+348
-4
lines changed

2 files changed

+348
-4
lines changed

arch/arm64/net/bpf_jit.h

+12-2
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,18 @@
8686
AARCH64_INSN_VARIANT_64BIT, \
8787
AARCH64_INSN_LDST_##ls##_PAIR_##type)
8888
/* Rn -= 16; Rn[0] = Rt; Rn[8] = Rt2; */
89-
#define A64_PUSH(Rt, Rt2, Rn) A64_LS_PAIR(Rt, Rt2, Rn, -16, STORE, PRE_INDEX)
89+
#define A64_PUSH(Rt, Rt2, Rn) \
90+
A64_LS_PAIR(Rt, Rt2, Rn, -16, STORE, PRE_INDEX)
9091
/* Rt = Rn[0]; Rt2 = Rn[8]; Rn += 16; */
91-
#define A64_POP(Rt, Rt2, Rn) A64_LS_PAIR(Rt, Rt2, Rn, 16, LOAD, POST_INDEX)
92+
#define A64_POP(Rt, Rt2, Rn) \
93+
A64_LS_PAIR(Rt, Rt2, Rn, 16, LOAD, POST_INDEX)
94+
95+
/* Rn[imm] = Xt1; Rn[imm + 8] = Xt2 */
96+
#define A64_STP(Xt1, Xt2, Xn, imm) \
97+
A64_LS_PAIR(Xt1, Xt2, Xn, imm, STORE, SIGNED_OFFSET)
98+
/* Xt1 = Rn[imm]; Xt2 = Rn[imm + 8] */
99+
#define A64_LDP(Xt1, Xt2, Xn, imm) \
100+
A64_LS_PAIR(Xt1, Xt2, Xn, imm, LOAD, SIGNED_OFFSET)
92101

93102
/* Load/store exclusive */
94103
#define A64_SIZE(sf) \
@@ -270,6 +279,7 @@
270279
#define A64_BTI_C A64_HINT(AARCH64_INSN_HINT_BTIC)
271280
#define A64_BTI_J A64_HINT(AARCH64_INSN_HINT_BTIJ)
272281
#define A64_BTI_JC A64_HINT(AARCH64_INSN_HINT_BTIJC)
282+
#define A64_NOP A64_HINT(AARCH64_INSN_HINT_NOP)
273283

274284
/* DMB */
275285
#define A64_DMB_ISH aarch64_insn_gen_dmb(AARCH64_INSN_MB_ISH)

arch/arm64/net/bpf_jit_comp.c

+336-2
Original file line numberDiff line numberDiff line change
@@ -1333,10 +1333,16 @@ static int validate_code(struct jit_ctx *ctx)
13331333

13341334
for (i = 0; i < ctx->idx; i++) {
13351335
u32 a64_insn = le32_to_cpu(ctx->image[i]);
1336-
13371336
if (a64_insn == AARCH64_BREAK_FAULT)
13381337
return -1;
13391338
}
1339+
return 0;
1340+
}
1341+
1342+
static int validate_ctx(struct jit_ctx *ctx)
1343+
{
1344+
if (validate_code(ctx))
1345+
return -1;
13401346

13411347
if (WARN_ON_ONCE(ctx->exentry_idx != ctx->prog->aux->num_exentries))
13421348
return -1;
@@ -1461,7 +1467,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog)
14611467
build_epilogue(&ctx);
14621468

14631469
/* 3. Extra pass to validate JITed code. */
1464-
if (validate_code(&ctx)) {
1470+
if (validate_ctx(&ctx)) {
14651471
bpf_jit_binary_free(header);
14661472
prog = orig_prog;
14671473
goto out_off;
@@ -1532,6 +1538,334 @@ void bpf_jit_free_exec(void *addr)
15321538
return vfree(addr);
15331539
}
15341540

1541+
static void invoke_bpf_prog(struct jit_ctx *ctx, struct bpf_prog *p,
1542+
int args_off, int retval_off, bool save_ret)
1543+
{
1544+
u32 *branch;
1545+
u64 enter_prog;
1546+
u64 exit_prog;
1547+
u8 tmp = bpf2a64[TMP_REG_1];
1548+
u8 r0 = bpf2a64[BPF_REG_0];
1549+
1550+
if (p->aux->sleepable) {
1551+
enter_prog = (u64)__bpf_prog_enter_sleepable;
1552+
exit_prog = (u64)__bpf_prog_exit_sleepable;
1553+
} else {
1554+
enter_prog = (u64)__bpf_prog_enter;
1555+
exit_prog = (u64)__bpf_prog_exit;
1556+
}
1557+
1558+
/* bl __bpf_prog_enter */
1559+
emit_addr_mov_i64(A64_R(0), (const u64)p, ctx);
1560+
emit_addr_mov_i64(tmp, enter_prog, ctx);
1561+
emit(A64_BLR(tmp), ctx);
1562+
1563+
/*
1564+
* if (__bpf_prog_enter(prog) == 0)
1565+
* goto skip_exec_of_prog;
1566+
*/
1567+
branch = ctx->image + ctx->idx;
1568+
emit(A64_NOP, ctx);
1569+
1570+
/* move return value to x19 */
1571+
emit(A64_MOV(1, A64_R(19), r0), ctx);
1572+
1573+
/* bl bpf_prog */
1574+
emit(A64_ADD_I(1, A64_R(0), A64_SP, args_off), ctx);
1575+
if (!p->jited)
1576+
emit_addr_mov_i64(A64_R(1), (const u64)p->insnsi, ctx);
1577+
emit_addr_mov_i64(tmp, (const u64)p->bpf_func, ctx);
1578+
emit(A64_BLR(tmp), ctx);
1579+
1580+
/* store return value */
1581+
if (save_ret)
1582+
emit(A64_STR64I(r0, A64_SP, retval_off), ctx);
1583+
1584+
if (ctx->image) {
1585+
int offset = &ctx->image[ctx->idx] - branch;
1586+
*branch = A64_CBZ(1, A64_R(0), offset);
1587+
}
1588+
1589+
/* bl __bpf_prog_exit */
1590+
emit_addr_mov_i64(A64_R(0), (const u64)p, ctx);
1591+
emit(A64_MOV(1, A64_R(1), A64_R(19)), ctx);
1592+
emit_addr_mov_i64(tmp, exit_prog, ctx);
1593+
emit(A64_BLR(tmp), ctx);
1594+
}
1595+
1596+
static void invoke_bpf_mod_ret(struct jit_ctx *ctx, struct bpf_tramp_progs *tp,
1597+
int args_off, int retval_off, u32 **branches)
1598+
{
1599+
int i;
1600+
1601+
/* The first fmod_ret program will receive a garbage return value.
1602+
* Set this to 0 to avoid confusing the program.
1603+
*/
1604+
emit(A64_STR64I(A64_ZR, A64_SP, retval_off), ctx);
1605+
for (i = 0; i < tp->nr_progs; i++) {
1606+
invoke_bpf_prog(ctx, tp->progs[i], args_off, retval_off, true);
1607+
/*
1608+
* if (*(u64 *)(sp + retval_off) != 0)
1609+
* goto do_fexit;
1610+
*/
1611+
emit(A64_LDR64I(A64_R(10), A64_SP, retval_off), ctx);
1612+
/* Save the location of branch, and generate a nop.
1613+
* This nop will be replaced with a cbnz later.
1614+
*/
1615+
branches[i] = ctx->image + ctx->idx;
1616+
emit(A64_NOP, ctx);
1617+
}
1618+
}
1619+
1620+
static void save_args(struct jit_ctx *ctx, int args_off, int nargs)
1621+
{
1622+
int i;
1623+
1624+
for (i = 0; i < nargs; i++) {
1625+
emit(A64_STR64I(i, A64_SP, args_off), ctx);
1626+
args_off += 8;
1627+
}
1628+
}
1629+
1630+
static void restore_args(struct jit_ctx *ctx, int args_off, int nargs)
1631+
{
1632+
int i;
1633+
1634+
for (i = 0; i < nargs; i++) {
1635+
emit(A64_LDR64I(i, A64_SP, args_off), ctx);
1636+
args_off += 8;
1637+
}
1638+
}
1639+
1640+
/*
1641+
* Based on the x86's implementation of arch_prepare_bpf_trampoline().
1642+
*
1643+
* We dpend on DYNAMIC_FTRACE_WITH_REGS to set return address and nop.
1644+
*
1645+
* fentry before bpf trampoline hooked:
1646+
* mov x9, x30
1647+
* nop
1648+
*
1649+
* fentry after bpf trampoline hooked:
1650+
* mov x9, x30
1651+
* bl <bpf_trampoline>
1652+
*
1653+
*/
1654+
static int prepare_trampoline(struct jit_ctx *ctx, struct bpf_tramp_image *im,
1655+
struct bpf_tramp_progs *tprogs, void *orig_call,
1656+
int nargs, u32 flags)
1657+
{
1658+
int i;
1659+
int stack_size;
1660+
int retaddr_off;
1661+
int regs_off;
1662+
int retval_off;
1663+
int args_off;
1664+
int nargs_off;
1665+
struct bpf_tramp_progs *fentry = &tprogs[BPF_TRAMP_FENTRY];
1666+
struct bpf_tramp_progs *fexit = &tprogs[BPF_TRAMP_FEXIT];
1667+
struct bpf_tramp_progs *fmod_ret = &tprogs[BPF_TRAMP_MODIFY_RETURN];
1668+
bool save_ret;
1669+
u32 **branches = NULL;
1670+
1671+
/*
1672+
* trampoline stack layout:
1673+
* [ parent ip ]
1674+
* [ FP ]
1675+
* SP + retaddr_off [ self ip ]
1676+
* FP [ FP ]
1677+
*
1678+
* sp + regs_off [ x19 ] callee-saved regs, currently
1679+
* only x19 is used
1680+
*
1681+
* SP + retval_off [ return value ] BPF_TRAMP_F_CALL_ORIG or
1682+
* BPF_TRAMP_F_RET_FENTRY_RET flags
1683+
*
1684+
* [ argN ]
1685+
* [ ... ]
1686+
* sp + args_off [ arg1 ]
1687+
*
1688+
* SP + nargs_off [ args count ]
1689+
*
1690+
* SP + 0 [ traced function ] BPF_TRAMP_F_IP_ARG flag
1691+
*/
1692+
1693+
stack_size = 0;
1694+
/* room for IP address argument */
1695+
if (flags & BPF_TRAMP_F_IP_ARG)
1696+
stack_size += 8;
1697+
1698+
nargs_off = stack_size;
1699+
/* room for args count */
1700+
stack_size += 8;
1701+
1702+
args_off = stack_size;
1703+
/* room for args */
1704+
stack_size += nargs * 8;
1705+
1706+
/* room for return value */
1707+
retval_off = stack_size;
1708+
save_ret = flags & (BPF_TRAMP_F_CALL_ORIG | BPF_TRAMP_F_RET_FENTRY_RET);
1709+
if (save_ret)
1710+
stack_size += 8;
1711+
1712+
/* room for callee-saved registers, currently only x19 is used */
1713+
regs_off = stack_size;
1714+
stack_size += 8;
1715+
1716+
retaddr_off = stack_size + 8;
1717+
1718+
if (IS_ENABLED(CONFIG_ARM64_BTI_KERNEL))
1719+
emit(A64_BTI_C, ctx);
1720+
1721+
/* frame for parent function */
1722+
emit(A64_PUSH(A64_FP, A64_R(9), A64_SP), ctx);
1723+
emit(A64_MOV(1, A64_FP, A64_SP), ctx);
1724+
1725+
/* frame for patched function */
1726+
emit(A64_PUSH(A64_FP, A64_LR, A64_SP), ctx);
1727+
emit(A64_MOV(1, A64_FP, A64_SP), ctx);
1728+
1729+
/* allocate stack space */
1730+
emit(A64_SUB_I(1, A64_SP, A64_SP, stack_size), ctx);
1731+
1732+
if (flags & BPF_TRAMP_F_IP_ARG) {
1733+
/* save ip address of the traced function */
1734+
emit_addr_mov_i64(A64_R(10), (const u64)orig_call, ctx);
1735+
emit(A64_STR64I(A64_R(10), A64_SP, 0), ctx);
1736+
}
1737+
1738+
/* save args count*/
1739+
emit(A64_MOVZ(1, A64_R(10), nargs, 0), ctx);
1740+
emit(A64_STR64I(A64_R(10), A64_SP, nargs_off), ctx);
1741+
1742+
/* save args */
1743+
save_args(ctx, args_off, nargs);
1744+
1745+
/* save callee saved registers */
1746+
emit(A64_STR64I(A64_R(19), A64_SP, regs_off), ctx);
1747+
1748+
if (flags & BPF_TRAMP_F_CALL_ORIG) {
1749+
emit_addr_mov_i64(A64_R(0), (const u64)im, ctx);
1750+
emit_addr_mov_i64(A64_R(10), (const u64)__bpf_tramp_enter, ctx);
1751+
emit(A64_BLR(A64_R(10)), ctx);
1752+
}
1753+
1754+
for (i = 0; i < fentry->nr_progs; i++)
1755+
invoke_bpf_prog(ctx, fentry->progs[i], args_off, retval_off,
1756+
flags & BPF_TRAMP_F_RET_FENTRY_RET);
1757+
1758+
if (fmod_ret->nr_progs) {
1759+
branches = kcalloc(fmod_ret->nr_progs, sizeof(u32 *),
1760+
GFP_KERNEL);
1761+
if (!branches)
1762+
return -ENOMEM;
1763+
1764+
invoke_bpf_mod_ret(ctx, fmod_ret, args_off, retval_off,
1765+
branches);
1766+
}
1767+
1768+
if (flags & BPF_TRAMP_F_CALL_ORIG) {
1769+
restore_args(ctx, args_off, nargs);
1770+
emit(A64_LDR64I(A64_R(10), A64_SP, retaddr_off), ctx);
1771+
emit(A64_BLR(A64_R(10)), ctx);
1772+
emit(A64_STR64I(A64_R(0), A64_SP, retval_off), ctx);
1773+
im->ip_after_call = ctx->image + ctx->idx;
1774+
emit(A64_NOP, ctx);
1775+
}
1776+
1777+
/* update the branches saved in invoke_bpf_mod_ret with cbnz */
1778+
for (i = 0; i < fmod_ret->nr_progs && ctx->image != NULL; i++) {
1779+
int offset = &ctx->image[ctx->idx] - branches[i];
1780+
*branches[i] = A64_CBNZ(1, A64_R(10), offset);
1781+
}
1782+
1783+
for (i = 0; i < fexit->nr_progs; i++)
1784+
invoke_bpf_prog(ctx, fexit->progs[i], args_off, retval_off,
1785+
false);
1786+
1787+
if (flags & BPF_TRAMP_F_RESTORE_REGS)
1788+
restore_args(ctx, args_off, nargs);
1789+
1790+
if (flags & BPF_TRAMP_F_CALL_ORIG) {
1791+
im->ip_epilogue = ctx->image + ctx->idx;
1792+
emit_addr_mov_i64(A64_R(0), (const u64)im, ctx);
1793+
emit_addr_mov_i64(A64_R(10), (const u64)__bpf_tramp_exit, ctx);
1794+
emit(A64_BLR(A64_R(10)), ctx);
1795+
}
1796+
1797+
/* restore x19 */
1798+
emit(A64_LDR64I(A64_R(19), A64_SP, regs_off), ctx);
1799+
1800+
if (save_ret)
1801+
emit(A64_LDR64I(A64_R(0), A64_SP, retval_off), ctx);
1802+
1803+
/* reset SP */
1804+
emit(A64_MOV(1, A64_SP, A64_FP), ctx);
1805+
1806+
/* pop frames */
1807+
emit(A64_POP(A64_FP, A64_LR, A64_SP), ctx);
1808+
emit(A64_POP(A64_FP, A64_R(9), A64_SP), ctx);
1809+
1810+
if (flags & BPF_TRAMP_F_SKIP_FRAME) {
1811+
/* skip patched function, return to parent */
1812+
emit(A64_MOV(1, A64_LR, A64_R(9)), ctx);
1813+
emit(A64_RET(A64_R(9)), ctx);
1814+
} else {
1815+
/* return to patched function */
1816+
emit(A64_MOV(1, A64_R(10), A64_LR), ctx);
1817+
emit(A64_MOV(1, A64_LR, A64_R(9)), ctx);
1818+
emit(A64_RET(A64_R(10)), ctx);
1819+
}
1820+
1821+
if (ctx->image)
1822+
bpf_flush_icache(ctx->image, ctx->image + ctx->idx);
1823+
1824+
kfree(branches);
1825+
1826+
return ctx->idx;
1827+
}
1828+
1829+
int arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *image,
1830+
void *image_end, const struct btf_func_model *m,
1831+
u32 flags, struct bpf_tramp_progs *tprogs,
1832+
void *orig_call)
1833+
{
1834+
int ret;
1835+
int nargs = m->nr_args;
1836+
int max_insns = ((long)image_end - (long)image) / AARCH64_INSN_SIZE;
1837+
struct jit_ctx ctx = {
1838+
.image = NULL,
1839+
.idx = 0
1840+
};
1841+
1842+
/* we depends on ftrace to set return address and place nop */
1843+
if (!IS_ENABLED(CONFIG_DYNAMIC_FTRACE_WITH_REGS))
1844+
return -ENOTSUPP;
1845+
1846+
/* the first 8 arguments are passed by registers */
1847+
if (nargs > 8)
1848+
return -ENOTSUPP;
1849+
1850+
ret = prepare_trampoline(&ctx, im, tprogs, orig_call, nargs, flags);
1851+
if (ret < 0)
1852+
return ret;
1853+
1854+
if (ret > max_insns)
1855+
return -EFBIG;
1856+
1857+
ctx.image = image;
1858+
ctx.idx = 0;
1859+
1860+
jit_fill_hole(image, (unsigned int)(image_end - image));
1861+
ret = prepare_trampoline(&ctx, im, tprogs, orig_call, nargs, flags);
1862+
1863+
if (ret > 0 && validate_code(&ctx) < 0)
1864+
ret = -EINVAL;
1865+
1866+
return ret;
1867+
}
1868+
15351869
static int gen_branch_or_nop(enum aarch64_insn_branch_type type, void *ip,
15361870
void *addr, u32 *insn)
15371871
{

0 commit comments

Comments
 (0)