---
title: Xilinx-XRT-Driver-Fuzzing
author: bynx
tags: fuzzing
---
# Xilinx XRT Linux Interfaces
The XRT platform utilizes two (2) drivers, seperated by privilege:
1. `xocl`: _binds to **user-space** PF1_
1. `xclmgmt`: _binds to **kernel-space** PF0_
<figure>
<center>
<img class=center-cropped />
<figcaption>
<i>Figure 1: Download flow for of an <code>xclbin</code> via Mailbox for `PF0` ➜ `PF1`</i>
</figcaption>
</center>
<style>
.center-cropped {
scale: 0.95;
width: 1000px;
height: 600px;
filter: invert(80%);
background-position: center bottom;
background-repeat: no-repeat;
background-image: url('https://xilinx.github.io/XRT/2020.2/html/_images/sw-mailbox-msd-mpd-download.svg');
}
</style>
</figure>
<!--<img src="linux-usb-subsystem-diagram.svg" />-->
From our perspective, we are specifically interested in _fuzzing `xocl` driver functions from
SW user-space_ (`PF0` ➜ `PF1`).
More explicity, this flow of data only occurs [over software] when the user intends to program
the FPGA compute fabric; XRT uses Dynamic Function Exchange (DFX) to load user-compiled binaries to the **User** partition
The _xclbin_ (image) '(down)load' itself is from the `xclmgmt` driver, which binds to `PF0`:
- `xclmgmt` downloads the bitstream packaged in the bitstream section of _xclbin_ by
programming the ICAP peripheral
- `xclmgmt` then discovers the target frequency of the User partition by reading the _xclbin_
clock section and then programs the clocks which are controlled from Shell
From the user-perspective, the equivalent is:
```bash
/opt/xilinx/xrt/bin/xbutil program --device <bsd> --user /path/to/fpga-img.xclbin
```
## Fuzzer Design Assumptions
Our assumptions:
- [ ] Our input source correctly targets our intended system-under-test (SUT)
(i.e., `xclbin` file ➜ `ioctl` syscall ➜ `icap` peripheral ➜ `...` ➜ `PF0` )
- [ ] No additional security restrictions have been placed beyond default XRT configurations
- _xclbin_ signing, IP rules, other stuff...
- [ ] _xclbin_ files can be constructed using specified syscalls, as defined by XRT
- [ ] `xocl` operates as an standard PCI/FPGA driver w.r.t. kernel-space
- [ ] Fuzzing the `xocl` driver's `ioctl` interface can be performed through an `mpd` plugin
These assumptions then allows us to treat our design ala (linux) kernel fuzzing, giving us our
first design component -- our fuzzer [syzkaller][syzk]
➜ The plan is to extend the current `sys-executer` to operate over Xilinx syscalls
## Syzkaller kernel fuzzer
### Plan
1. Extend the syscall input space to allow fuzzing of _xclbin_ files from `PF1` [<sup>$\dagger$</sup>][syz-newsyscall] [<sup>$\ddagger$</sup>][syz-syscalldef]
3. Launch syzkaller to fuzz the linux kernel, but exlcude irrellevant syscalls
(i.e., focus in on XRT/`xocl` PCIe driver)
1. Backup: Use `xoxl` struct and function signatures to hook in a dumb fuzzer for `ioctl`
<sup>
<br>
[_<sup>$\dagger$</sup>_ _Describing new system calls_][syz-newsyscall]
[_<sup>$\ddagger$</sup>_ _Syscall description language_][syz-syscalldef]
</sup>
#### Example Extension Syntax
```c
/* Example of sys-execute syscall syntax */
/*
* https://github.com/google/syzkaller/blob/master/sys/openbsd/dev_pci.txt
*/
include <sys/types.h>
include <sys/pciio.h>
include <fcntl.h>
resource fd_pci[fd]
openat$pci(fd const[AT_FDCWD], file ptr[in, string["/dev/pci"]], flags flags[open_flags], mode const[0]) fd_pci
ioctl$PCIOCREAD(fd fd_pci, cmd const[PCIOCREAD], arg ptr[out, pci_io])
ioctl$PCIOCWRITE(fd fd_pci, cmd const[PCIOCWRITE], arg ptr[in, pci_io])
ioctl$PCIOCGETROM(fd fd_pci, cmd const[PCIOCGETROM], arg ptr[out, pci_rom])
pci_io {
pi_sel pcisel
pi_reg int32
pi_width int32
pi_data int32
}
pci_rom {
pr_sel pcisel
pr_romlen len[pr_rom, int32]
pr_rom ptr[out, array[int32]]
}
pcisel {
pc_bus int8
pc_dev int8
pc_func int8
}
/*
* https://github.com/google/syzkaller/blob/master/sys/openbsd/dev_pci.txt.const
*/
# Code generated by syz-sysgen. DO NOT EDIT.
arches = amd64
AT_FDCWD = amd64:18446744073709551516
PCIOCGETROM = amd64:3222302725
PCIOCREAD = amd64:3222302722
PCIOCWRITE = amd64:3222302723
SYS_ioctl = amd64:54
SYS_openat = amd64:321
```
## Harness Construction: Execution Flow Tracing
1. `xbutil program --device <bsd> --user /path/to/hello-world.xclbin`
- [src fn][xbutil-program] corresponding to: `xbutil program <...>`
```c++
/*
* xbutil program --device <bsd> --user /path/to/hello-world.xclbin
*/
int program(const std::string& xclbin, unsigned region) {
std::ifstream stream(xclbin.c_str());
if(!stream.is_open()) {
std::cout
<< "ERROR: Cannot open "
<< xclbin
<< ". Check that it exists and is readable."
<< std::endl;
return -ENOENT;
}
if(region) {
std::cout << "ERROR: Not support other than -r 0 " << std::endl;
return -EINVAL;
}
char temp_8_;
stream.read(temp, 8);
if (std::strncmp(temp, "xclbin0", 8)) {
if (std::strncmp(temp, "xclbin2", 8))
return -EINVAL;
}
stream.seekg(0, stream.end);
int length = stream.tellg();
stream.seekg(0, stream.beg);
std::vector<char> buffer(length);
stream.read(buffer.data(), length);
auto header = reinterpret_cast<const xclBin *>(buffer.data());
int result = xclLoadXclBin(m_handle, header); // !!!!!!!!!
return result;
}
```
2. `xclLoadXclBin(_,_)` [source][xclload]
```c++
/*
* Parent: int result = xclLoadXclBin(m_handle, header);
*/
int shim::xclLoadXclBin(const xclBin *buffer)
{
auto top = reinterpret_cast<const axlf*>(buffer);
auto ret = xclLoadAxlf(top); // !!!!!!!!!
if (ret != 0) {
if (ret == -EOPNOTSUPP) {
xrt_logmsg(XRT_ERROR, "Xclbin does not match shell on card.");
auto xclbin_vbnv = xrt_core::xclbin::get_vbnv(top);
auto shell_vbnv = xrt_core::device_query<xrt_core::query::rom_vbnv>(mCoreDevice);
if (xclbin_vbnv != shell_vbnv) {
xrt_logmsg(XRT_ERROR, "Shell VBNV is '%s'", shell_vbnv.c_str());
xrt_logmsg(XRT_ERROR, "Xclbin VBNV is '%s'", xclbin_vbnv.c_str());
}
xrt_logmsg(XRT_ERROR, "Use 'xbmgmt flash' to update shell.");
}
else if (ret == -EBUSY) {
xrt_logmsg(XRT_ERROR, "Xclbin on card is in use, can't change.");
}
else if (ret == -EKEYREJECTED) {
xrt_logmsg(XRT_ERROR, "Xclbin isn't signed properly");
}
else if (ret == -E2BIG) {
xrt_logmsg(XRT_ERROR, "Not enough host_mem for xclbin");
}
else if (ret == -ETIMEDOUT) {
xrt_logmsg(XRT_ERROR,
"Can't reach out to mgmt for xclbin downloading");
xrt_logmsg(XRT_ERROR,
"Is xclmgmt driver loaded? Or is MSD/MPD running?");
}
else if (ret == -EDEADLK) {
xrt_logmsg(XRT_ERROR, "CU was deadlocked? Hardware is not stable");
xrt_logmsg(XRT_ERROR, "Please reset device with 'xbutil reset'");
}
xrt_logmsg(XRT_ERROR, "See dmesg log for details. err=%d", ret);
}
return ret;
}
```
3. `xclLoadAxlf(_,_)` [source][xclload2]
```c++
/*
* Parent: auto ret = xclLoadAxlf(top);
*/
int shim::xclLoadAxlf(const axlf *buffer)
{
xrt_logmsg(XRT_INFO, "%s, buffer: %s", __func__, buffer);
drm_xocl_axlf axlf_obj = {const_cast<axlf *>(buffer), 0};
int off = 0;
auto kernels = xrt_core::xclbin::get_kernels(buffer);
/* Calculate size of kernels */
for (auto& kernel : kernels) {
axlf_obj.ksize += sizeof(kernel_info) + sizeof(argument_info) * kernel.args.size();
}
/* To enhance CU subdevice and KDS/ERT, driver needs all details about kernels
* while load xclbin.
*
* Why we extract data from XML metadata?
* 1. Kernel is NOT a good place to parse xml. It prefers binary.
* 2. All kernel details are in the xml today.
*
* What would happen in the future?
* XCLBIN would contain fdt as metadata. At that time, this
* could be removed.
*
* Binary format:
* +-----------------------+
* | Kernel[0] |
* | name[64] |
* | anums |
* | argument[0] |
* | argument[1] |
* | argument[...] |
* |-----------------------|
* | Kernel[1] |
* | name[64] |
* | anums |
* | argument[0] |
* | argument[1] |
* | argument[...] |
* |-----------------------|
* | Kernel[...] |
* | ... |
* +-----------------------+
*/
std::vector<char> krnl_binary(axlf_obj.ksize);
axlf_obj.kernels = krnl_binary.data();
for (auto& kernel : kernels) {
auto krnl = reinterpret_cast<kernel_info *>(axlf_obj.kernels + off);
if (kernel.name.size() > sizeof(krnl->name))
return -EINVAL;
std::strncpy(krnl->name, kernel.name.c_str(), sizeof(krnl->name)-1);
krnl->name[sizeof(krnl->name)-1] = '\0';
krnl->anums = kernel.args.size();
krnl->range = kernel.range;
int ai = 0;
for (auto& arg : kernel.args) {
if (arg.name.size() > sizeof(krnl->args[ai].name)) {
xrt_logmsg(XRT_ERROR, "%s: Argument name length %d>%d", __func__, arg.name.size(), sizeof(krnl->args[ai].name));
return -EINVAL;
}
std::strncpy(krnl->args[ai].name, arg.name.c_str(), sizeof(krnl->args[ai].name)-1);
krnl->args[ai].name[sizeof(krnl->args[ai].name)-1] = '\0';
krnl->args[ai].offset = arg.offset;
krnl->args[ai].size = arg.size;
// XCLBIN doesn't define argument direction yet and it only support
// input arguments.
// Driver use 1 for input argument and 2 for output.
// Let's refine this line later.
krnl->args[ai].dir = 1;
ai++;
}
off += sizeof(kernel_info) + sizeof(argument_info) * kernel.args.size();
}
/* To make download xclbin and configure KDS/ERT as an atomic operation. */
axlf_obj.kds_cfg.ert = xrt_core::config::get_ert();
axlf_obj.kds_cfg.polling = xrt_core::config::get_ert_polling();
axlf_obj.kds_cfg.cu_dma = xrt_core::config::get_ert_cudma();
axlf_obj.kds_cfg.cu_isr = xrt_core::config::get_ert_cuisr() && xrt_core::xclbin::get_cuisr(buffer);
axlf_obj.kds_cfg.cq_int = xrt_core::config::get_ert_cqint();
axlf_obj.kds_cfg.dataflow = xrt_core::config::get_feature_toggle("Runtime.dataflow") || xrt_core::xclbin::get_dataflow(buffer);
axlf_obj.kds_cfg.rw_shared = xrt_core::config::get_rw_shared();
/* TODO: In scheduler.cpp init() function, it use get_ert_slots(void) to get slot size.
* But we cannot do this here, since the xclbin is not registered.
* Currently, emulation flow use get_ert_slots() as well.
* We will consider how to better determine slot size in new kds.
*/
//axlf_obj.kds_cfg.slot_size = mCoreDevice->get_ert_slots().second;
auto xml_hdr = xrt_core::xclbin::get_axlf_section(buffer, EMBEDDED_METADATA);
if (!xml_hdr)
throw std::runtime_error("No xml metadata in xclbin");
auto xml_size = xml_hdr->m_sectionSize;
auto xml_data = reinterpret_cast<const char*>(reinterpret_cast<const char*>(buffer) + xml_hdr->m_sectionOffset);
axlf_obj.kds_cfg.slot_size = mCoreDevice->get_ert_slots(xml_data, xml_size).second;
int ret = mDev->ioctl(mUserHandle, DRM_IOCTL_XOCL_READ_AXLF, &axlf_obj); // !!!!!!!!!
if(ret)
return -errno;
// If it is an XPR DSA, zero out the DDR again as downloading the XCLBIN
// reinitializes the DDR and results in ECC error.
if(isXPR())
{
xrt_logmsg(XRT_INFO, "%s, XPR Device found, zeroing out DDR again..", __func__);
if (zeroOutDDR() == false)
{
xrt_logmsg(XRT_ERROR, "%s, zeroing out DDR again..", __func__);
return -EIO;
}
}
return ret;
}
```
4. This leaves us at the raw `ioctl` [syscall][ioctl-shim]:
```c++
/*
* FnSig: int ioctl(int fd, cmd const[DRM_IOCTL_XOCL], const axlf* buf)
*/
int ret = mDev->ioctl(mUserHandle, DRM_IOCTL_XOCL_READ_AXLF, &axlf_obj);
```
## Appendix A: Quick Definitions
### **_xclbin_**
- **_What is it?_**
➜ A contained which packs FPGA bitstreams and collections of metadata (i.e., memory
topology, IP instantiations, etc).
- **_What does it look like?_**
➜ [_xclbin_ format definition][xclbin-fmt]
#### **_Mailbox Subdevice Drive (`msd`)_**
- **_What is it?_**
➜ A link between two (2), otherwise separate, processor systems
- **_What does it do?_**
➜ Bi-directional IPC; Generate interrupts between the processors
- **_What does it look like?_**
➜ [mailbox_msg][mailbox-msg] and [mailbox_protocol][mailbox-proto]
(`src/runtime_src/core/pcie/driver/linux/xocl/subdev/mailbox.c`)
### **_xoxl_**
> Xilinx® PCIe platforms like Alveo PCIe cards support various memory topologies which
> can be dynamically loaded as part of FPGA image loading step. This means from one
> FPGA image to another the device may expose one or more memory controllers where
> each memory controller has its own memory address range. We use Linux `_drm_mm_` for
> allocation of memory and Linux _drm_gem_ framework for mmap handling. Since ordinarily
> our device memory is not exposed to host CPU (except when we enable PCIe peer-to-peer
> feature) we use host memory pages to back device memory for mmap support. For
> syncing between device memory and host memory pages XDMA/QDMA PCIe memory
> mapped DMA engine is used. Users call sync ioctl to effect DMA in requested direction.
[xocl execution model][xocl-execmodel]
### **_xclmgmt_**
> XRT Linux kernel driver _xclmgmt_ binds to management physical function. Management
> physical function provides access to Shell components responsible for **privileged**
> operations. xclmgmt driver is organized into subdevices and handles the following
> functionality:
### **_data flow breakdown_**
A typical **user** execution flow (via **PF1**):
> 1. **Load _xclbin_** using `DOWNLOAD` `ioctl`
> 1. Discover compute unit register map from xclbin
> 1. **Allocate data buffers** to feed compute units using `CREATE_BO/MAP_BO` `ioctl` calls
> 1. Migrate input data buffers from host to device using `SYNC_BO` `ioctl`
> 1. **Allocate an execution command buffer** using `CREATE_BO/MAP_BO` `ioctl` call and fill
> command buffer using data in 2 above and following the format defined in `ert.h`
> 1. **Submit the execution command buffer** using `EXECBUF` `ioctl`
> 1. Wait for completion using POSIX `poll`
> 1. Migrate output data buffers from device to host using `SYNC_BO` `ioctl`
> 1. **Release data buffers and command buffer**
### misc but helpful probs
- `xclbin_parser` [source][xclbin-parser]
- software channel of mailbox [explained][mailbox-src-sw]
- `msd` plugin [example src][msd-plug]
- [linux-xlnx][nix-xlnx] PCIe controller functions for Xilinix Linux Kernel
- [custom xclbin protections][xrt-container]
[xbutil-program]: https://github.com/Xilinx/XRT/blob/master/src/runtime_src/core/pcie/tools/xbutil/xbutil.h#L1768-L1803
[xclload]: https://github.com/Xilinx/XRT/blob/master/src/runtime_src/core/pcie/linux/shim.cpp#L1308-L1349
[xclload2]: https://github.com/Xilinx/XRT/blob/master/src/runtime_src/core/pcie/linux/shim.cpp#L1351-L1467
[ioctl-shim]: https://github.com/Xilinx/XRT/blob/master/src/runtime_src/core/edge/user/shim.cpp#L578
[icap-user]:https://github.com/Xilinx/XRT/blob/master/src/runtime_src/core/pcie/driver/linux/xocl/subdev/icap.c#L2388-L2453
[icap-kernel]: https://github.com/Xilinx/XRT/blob/master/src/runtime_src/core/pcie/driver/linux/xocl/subdev/icap.c#L2455-L2530
[icap-shared]: https://github.com/Xilinx/XRT/blob/master/src/runtime_src/core/pcie/driver/linux/xocl/subdev/icap.c#L2532-L2629
[syzk]: https://github.com/google/syzkaller/
[syz-newsyscall]: https://github.com/google/syzkaller/blob/master/docs/syscall_descriptions.md#describing-new-system-calls
[syz-syscalldef]: https://github.com/google/syzkaller/blob/master/docs/syscall_descriptions_syntax.md
[xclbin-fmt]: https://xilinx.github.io/XRT/2020.2/html/formats.html#formats-rst
[mailbox-msg]: https://github.com/Xilinx/XRT/blob/master/src/runtime_src/core/pcie/driver/linux/xocl/subdev/mailbox.c#L281-L299
[mailbox-proto]: https://github.com/Xilinx/XRT/blob/master/src/runtime_src/core/pcie/driver/linux/include/mailbox_proto.h
[xocl-execmodel]: https://xilinx.github.io/XRT/2020.2/html/execution-model.html#xocl
[xclbin-parser]: https://github.com/Xilinx/XRT/blob/master/src/runtime_src/core/common/xclbin_parser.cpp
[mailbox-src-sw]: https://github.com/Xilinx/XRT/blob/master/src/runtime_src/core/pcie/driver/linux/xocl/subdev/mailbox.c#L142
[msd-plug]: https://github.com/Xilinx/XRT/blob/master/src/runtime_src/core/pcie/tools/cloud-daemon/msd_plugin_example.c
[nix-xlnx]: https://github.com/Xilinx/linux-xlnx/blob/master/drivers/pci/controller/pcie-xilinx.c#L96
[xrt-container]: https://github.com/Xilinx/XRT/blob/master/src/runtime_src/core/pcie/tools/cloud-daemon/container/container.cpp#L167