Pre-submission Checklist
GPU Hardware
Intel Arc Pro B70
DRI Devices Information
$ ls -ls /dev/dri/*
0 crw-rw----+ 1 root video 226, 0 Aug 23 06:09 /dev/dri/card0
0 crw-rw----+ 1 root video 226, 1 Aug 23 06:09 /dev/dri/card1
0 crw-rw----+ 1 root render 226, 128 Aug 23 06:09 /dev/dri/renderD128
$ ls -la /dev/dri/by-path/
total 0
drwxr-xr-x 2 root root 100 Aug 23 06:09 .
drwxr-xr-x 3 root root 120 Aug 23 06:09 ..
lrwxrwxrwx+ 1 root root 8 Aug 23 06:09 pci-0000:02:00.0-card -> ../card0
lrwxrwxrwx+ 1 root root 8 Aug 23 06:09 pci-0000:ad:00.0-card -> ../card1
lrwxrwxrwx+ 1 root root 13 Aug 23 06:09 pci-0000:ad:00.0-render -> ../renderD128
The GPU is 0000:ad:00.0 (card1 / renderD128). card0 at 0000:02:00.0 is the
onboard display controller and has no render node.
GPU Detailed Information (lspci output)
$ sudo lspci -vvv -k -s 0000:ad:00.0
ad:00.0 VGA compatible controller: Intel Corporation Battlemage G31 [Intel Graphics] (prog-if 00 [VGA controller])
Subsystem: Intel Corporation Device 1701
Control: I/O+ Mem+ BusMaster+ SpecCycle- MemWINV- VGASnoop- ParErr+ Stepping- SERR+ FastB2B- DisINTx-
Status: Cap+ 66MHz- UDF- FastB2B- ParErr- DEVSEL=fast >TAbort- <TAbort- <MAbort- >SERR- <PERR- INTx-
Latency: 0, Cache Line Size: 32 bytes
Interrupt: pin ? routed to IRQ 1064
NUMA node: 1
IOMMU group: 50
Region 0: Memory at e0000000 (64-bit, non-prefetchable) [size=16M]
Region 2: Memory at 227000000000 (64-bit, prefetchable) [size=32G]
Expansion ROM at e1000000 [disabled] [size=2M]
Capabilities: [70] Express (v2) Endpoint, IntMsgNum 0
DevCap: MaxPayload 256 bytes, PhantFunc 0, Latency L0s unlimited, L1 unlimited
ExtTag+ AttnBtn- AttnInd- PwrInd- RBE+ FLReset+ SlotPowerLimit 0W TEE-IO-
DevCtl: CorrErr- NonFatalErr- FatalErr+ UnsupReq-
RlxdOrd+ ExtTag+ PhantFunc- AuxPwr- NoSnoop+ FLReset-
MaxPayload 256 bytes, MaxReadReq 512 bytes
DevSta: CorrErr- NonFatalErr- FatalErr- UnsupReq- AuxPwr- TransPend-
LnkCap: Port #0, Speed 2.5GT/s, Width x1, ASPM L0s L1, Exit Latency L0s <64ns, L1 <1us
LnkSta: Speed 2.5GT/s, Width x1
Capabilities: [ac] MSI: Enable+ Count=1/1 Maskable+ 64bit+
Capabilities: [d0] Power Management version 3
Capabilities: [100 v1] Alternative Routing-ID Interpretation (ARI)
Capabilities: [200 v1] Address Translation Service (ATS)
ATSCap: Invalidate Queue Depth: 00
ATSCtl: Enable+, Smallest Translation Unit: 00
Capabilities: [420 v1] Physical Resizable BAR
BAR 2: current size: 32GB, supported: 256MB 512MB 1GB 2GB 4GB 8GB 16GB 32GB
Capabilities: [400 v1] Latency Tolerance Reporting
Kernel driver in use: xe
Kernel modules: xe
Trimmed to the interesting parts — PCIe equalization/preset tables removed.
Driver Version
26.31.39395.13
Installed GPU Driver Packages
$ sudo dpkg --list | grep -iE "igc|gmm|opencl|level-zero|fc|level_zero|ocloc|libze"
ii clinfo 3.0.25.02.14-1build1 amd64
ii intel-ocloc 26.31.39395.13-1~26.04~ppa1 amd64
ii intel-opencl-icd 26.31.39395.13-1~26.04~ppa1 amd64
ii libigc2 2.40.13-1~26.04 amd64
ii libigdfcl2 2.40.13-1~26.04 amd64
ii libigdgmm12:amd64 22.10.0-1~26.04~ppa1 amd64
ii libze-dev:amd64 1.32.0-1~26.04~ppa1 amd64
ii libze-intel-gpu1 26.31.39395.13-1~26.04~ppa1 amd64
ii libze1:amd64 1.32.0-1~26.04~ppa1 amd64
ii ocl-icd-libopencl1:amd64 2.3.4-1 amd64
Driver Installation Details
Prebuilt .deb packages installed with apt from the Intel PPA
(ppa:kobuk-team/intel-graphics-staging, resolute suite) on Ubuntu 26.04 LTS.
Nothing built from source, nothing side-loaded, no ZE_ENABLE_ALT_DRIVERS and no
LD_LIBRARY_PATH override — this is the stock supported install. LD_DEBUG=libs
confirms the system libraries are the ones doing the work:
$ LD_DEBUG=libs ./zeprobe-reuse 1 2>&1 | grep 'calling init'
calling init: /usr/lib/x86_64-linux-gnu/libze_intel_gpu.so.1
calling init: /usr/lib/x86_64-linux-gnu/libigdfcl.so.2
calling init: /usr/lib/x86_64-linux-gnu/libigc.so.2
Linux Distribution
Other (please specify below)
Other Linux Distribution
Ubuntu 26.04 LTS (resolute)
Kernel Version & Boot Parameters
$ uname -a
Linux smc-test02 7.0.0+ #1 SMP PREEMPT_DYNAMIC Tue Jul 21 15:00:43 UTC 2026 x86_64 GNU/Linux
$ cat /proc/cmdline
BOOT_IMAGE=/vmlinuz-7.0.0+ root=/dev/mapper/ubuntu--vg-ubuntu--lv ro
i915.force_probe=!e210 xe.force_probe=e210 nvme_core.multipath=N
intel_iommu=on iommu=pt xe.max_vfs=16 console=tty0 console=ttyS1,115200n8
crashkernel=2G-4G:320M,4G-32G:512M,32G-64G:1024M,64G-128G:2048M,128G-:4096M
Actual Behavior
Each zeMemOpenIpcHandle() call leaves behind one extra /dmabuf: fd that never goes
away. zeMemCloseIpcHandle() doesn't release it — if I skip the Close call entirely
the fd count comes out exactly the same. zeContextDestroy() doesn't reclaim them
either. Every call returns ZE_RESULT_SUCCESS, so nothing warns you it's happening.
30 balanced Open/Close pairs on a single IPC handle leave 30 fds behind:
before opens: 1 <- the fd carried by the IPC handle
after open/close 1: 3 <- +2 (the extra one looks like one-time driver init)
after open/close 2: 4 <- +1
...
after open/close 30: 32
after cleanup: 31 <- Put + Free + ContextDestroy gave back exactly one
Counting after every individual call in a full cycle shows where it comes from:
after zeMemAllocDevice dmabuf_fds=0
after zeMemGetIpcHandle dmabuf_fds=1 (handle carries fd 4)
after zeMemOpenIpcHandle dmabuf_fds=3 +2 here, +1 on later iterations
after zeMemCloseIpcHandle dmabuf_fds=3 no change
after zeMemPutIpcHandle dmabuf_fds=2 releases the handle's fd
after zeMemFree dmabuf_fds=2
after zeContextDestroy dmabuf_fds=2
To get ahead of the obvious question: this isn't me forgetting to close an fd I was
handed. The only descriptor I ever see is the one inside ze_ipc_mem_handle_t, and
zeMemPutIpcHandle() clearly does release that one — you can watch it go from 1 back
to 0 above. What leaks is a second fd created inside zeMemOpenIpcHandle() that I'm
never given a reference to, so there's nothing I could close even if I wanted to, and
zeMemCloseIpcHandle() leaves it alone.
Since the loop reuses one handle, this also shows the driver isn't refcounting and
handing back the same mapping — every Open produces a distinct mapping, and closing
it frees nothing.
Expected Behavior
zeMemCloseIpcHandle() should release whatever zeMemOpenIpcHandle() allocated,
internal dmabuf fds included, so the process fd count stays flat no matter how many
balanced Open/Close pairs you do. Failing that, zeContextDestroy() should at least
clean up on the way out.
Reproduction Rate
Always reproduces - 100%
Steps to Reproduce
- Save the program from the Source Code field below as
zeprobe-reuse.c.
- Build it:
gcc -O0 -g -o zeprobe-reuse zeprobe-reuse.c -lze_loader
- Run it:
./zeprobe-reuse 30
It allocates one device buffer, exports one IPC handle, then does 30 balanced
zeMemOpenIpcHandle/zeMemCloseIpcHandle pairs against that same handle, counting
entries in /proc/self/fd whose readlink target contains dmabuf after each pair.
Nothing else happens inside the loop — no allocations, no Get/Put — so the growth
can only come from the Open/Close pair.
The count should stay flat. Instead it grows by one per iteration, without bound, until the process hits RLIMIT_NOFILE. ./zeprobe-reuse 1000 ends at after open/close 1000: 1002 and after cleanup: 1001 — 1000 leaked, plus the handle's own fd and one from driver init. At that point the process is already close to the default 1024 soft limit.
Is this a regression?
Last Known Working Driver Version
None known - appears to have never worked
First Known Failing Driver Version
Unknown - oldest version tested (26.18.38308.1) is already affected
API Call Logs
Nothing to show, which is part of the problem: every call in the sequence returns
ZE_RESULT_SUCCESS. zeMemCloseIpcHandle() reports success while releasing nothing,
so the leak is completely silent from the API's point of view.
strace Logs
Not attached, but happy to provide if useful. /proc/self/fd readlink targets render
as /dmabuf:, which is how the reproducer identifies them.
System Logs / dmesg Output
Nothing in dmesg correlates with the leaking calls — no xe errors or warnings
appear during a probe run, and the count grows identically whether or not anything is
logged.
Backtrace (if crash or hang occurred)
Not applicable, there's no crash. The process runs to completion and exits cleanly;
it just accumulates descriptors until it would hit RLIMIT_NOFILE.
Source Code / Reproducer
#include <level_zero/ze_api.h>
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define ZCHECK(_call) \
do { \
ze_result_t _result = (_call); \
if (_result != ZE_RESULT_SUCCESS) { \
fprintf(stderr, "%s failed: 0x%x\n", #_call, _result); \
return 1; \
} \
} while (0)
static int count_dmabuf_fds(void)
{
char path[256];
char target[256];
struct dirent *entry;
DIR *dir;
ssize_t length;
int count = 0;
dir = opendir("/proc/self/fd");
if (dir == NULL) {
return -1;
}
while ((entry = readdir(dir)) != NULL) {
if (entry->d_name[0] == '.') {
continue;
}
snprintf(path, sizeof(path), "/proc/self/fd/%s", entry->d_name);
length = readlink(path, target, sizeof(target) - 1);
if (length <= 0) {
continue;
}
target[length] = '\0';
if (strstr(target, "dmabuf") != NULL) {
++count;
}
}
closedir(dir);
return count;
}
int main(int argc, char **argv)
{
int iterations = (argc > 1) ? atoi(argv[1]) : 30;
ze_context_desc_t context_desc = {};
ze_device_mem_alloc_desc_t alloc_desc = {};
ze_driver_handle_t driver;
ze_device_handle_t device;
ze_context_handle_t context;
ze_ipc_mem_handle_t handle;
uint32_t driver_count = 1;
uint32_t device_count = 1;
void *allocation;
void *mapping;
int i;
ZCHECK(zeInit(0));
ZCHECK(zeDriverGet(&driver_count, &driver));
ZCHECK(zeDeviceGet(driver, &device_count, &device));
context_desc.stype = ZE_STRUCTURE_TYPE_CONTEXT_DESC;
alloc_desc.stype = ZE_STRUCTURE_TYPE_DEVICE_MEM_ALLOC_DESC;
ZCHECK(zeContextCreate(driver, &context_desc, &context));
ZCHECK(zeMemAllocDevice(context, &alloc_desc, 4096, 64, device,
&allocation));
ZCHECK(zeMemGetIpcHandle(context, allocation, &handle));
printf("before opens: %d\n", count_dmabuf_fds());
for (i = 1; i <= iterations; ++i) {
ZCHECK(zeMemOpenIpcHandle(context, device, handle, 0, &mapping));
ZCHECK(zeMemCloseIpcHandle(context, mapping));
printf("after open/close %d: %d\n", i, count_dmabuf_fds());
}
ZCHECK(zeMemPutIpcHandle(context, handle));
ZCHECK(zeMemFree(context, allocation));
ZCHECK(zeContextDestroy(context));
printf("after cleanup: %d\n", count_dmabuf_fds());
return 0;
}
Command Line / Application Details
gcc -O0 -g -o zeprobe-reuse zeprobe-reuse.c -lze_loader
./zeprobe-reuse 30
No environment variables set. Single GPU visible, so no ZE_AFFINITY_MASK either.
oneAPI Version (if applicable)
Not used - reproducer links only libze_loader from the distro packages
Screenshots / Video
Not applicable — console output only, included above.
Additional Notes
I tried this on two unrelated machines across three driver releases and got to the same result, so it doesn't seem tied to a particular box, SKU or release.
Pre-submission Checklist
GPU Hardware
Intel Arc Pro B70
DRI Devices Information
The GPU is
0000:ad:00.0(card1 / renderD128).card0at0000:02:00.0is theonboard display controller and has no render node.
GPU Detailed Information (lspci output)
Trimmed to the interesting parts — PCIe equalization/preset tables removed.
Driver Version
26.31.39395.13
Installed GPU Driver Packages
Driver Installation Details
Prebuilt .deb packages installed with apt from the Intel PPA
(
ppa:kobuk-team/intel-graphics-staging,resolutesuite) on Ubuntu 26.04 LTS.Nothing built from source, nothing side-loaded, no
ZE_ENABLE_ALT_DRIVERSand noLD_LIBRARY_PATHoverride — this is the stock supported install.LD_DEBUG=libsconfirms the system libraries are the ones doing the work:
Linux Distribution
Other (please specify below)
Other Linux Distribution
Ubuntu 26.04 LTS (resolute)
Kernel Version & Boot Parameters
Actual Behavior
Each
zeMemOpenIpcHandle()call leaves behind one extra/dmabuf:fd that never goesaway.
zeMemCloseIpcHandle()doesn't release it — if I skip theClosecall entirelythe fd count comes out exactly the same.
zeContextDestroy()doesn't reclaim themeither. Every call returns
ZE_RESULT_SUCCESS, so nothing warns you it's happening.30 balanced Open/Close pairs on a single IPC handle leave 30 fds behind:
Counting after every individual call in a full cycle shows where it comes from:
To get ahead of the obvious question: this isn't me forgetting to close an fd I was
handed. The only descriptor I ever see is the one inside
ze_ipc_mem_handle_t, andzeMemPutIpcHandle()clearly does release that one — you can watch it go from 1 backto 0 above. What leaks is a second fd created inside
zeMemOpenIpcHandle()that I'mnever given a reference to, so there's nothing I could close even if I wanted to, and
zeMemCloseIpcHandle()leaves it alone.Since the loop reuses one handle, this also shows the driver isn't refcounting and
handing back the same mapping — every
Openproduces a distinct mapping, and closingit frees nothing.
Expected Behavior
zeMemCloseIpcHandle()should release whateverzeMemOpenIpcHandle()allocated,internal dmabuf fds included, so the process fd count stays flat no matter how many
balanced Open/Close pairs you do. Failing that,
zeContextDestroy()should at leastclean up on the way out.
Reproduction Rate
Always reproduces - 100%
Steps to Reproduce
zeprobe-reuse.c.gcc -O0 -g -o zeprobe-reuse zeprobe-reuse.c -lze_loader./zeprobe-reuse 30It allocates one device buffer, exports one IPC handle, then does 30 balanced
zeMemOpenIpcHandle/zeMemCloseIpcHandlepairs against that same handle, countingentries in
/proc/self/fdwhose readlink target containsdmabufafter each pair.Nothing else happens inside the loop — no allocations, no
Get/Put— so the growthcan only come from the Open/Close pair.
The count should stay flat. Instead it grows by one per iteration, without bound, until the process hits
RLIMIT_NOFILE../zeprobe-reuse 1000ends atafter open/close 1000: 1002andafter cleanup: 1001— 1000 leaked, plus the handle's own fd and one from driver init. At that point the process is already close to the default 1024 soft limit.Is this a regression?
Last Known Working Driver Version
None known - appears to have never worked
First Known Failing Driver Version
Unknown - oldest version tested (26.18.38308.1) is already affected
API Call Logs
Nothing to show, which is part of the problem: every call in the sequence returns
ZE_RESULT_SUCCESS.zeMemCloseIpcHandle()reports success while releasing nothing,so the leak is completely silent from the API's point of view.
strace Logs
Not attached, but happy to provide if useful.
/proc/self/fdreadlink targets renderas
/dmabuf:, which is how the reproducer identifies them.System Logs / dmesg Output
Nothing in
dmesgcorrelates with the leaking calls — noxeerrors or warningsappear during a probe run, and the count grows identically whether or not anything is
logged.
Backtrace (if crash or hang occurred)
Not applicable, there's no crash. The process runs to completion and exits cleanly;
it just accumulates descriptors until it would hit
RLIMIT_NOFILE.Source Code / Reproducer
Command Line / Application Details
No environment variables set. Single GPU visible, so no
ZE_AFFINITY_MASKeither.oneAPI Version (if applicable)
Screenshots / Video
Not applicable — console output only, included above.
Additional Notes
I tried this on two unrelated machines across three driver releases and got to the same result, so it doesn't seem tied to a particular box, SKU or release.