Installing gdb on Fedora 45 does not let it attach to a running process. The package installs cleanly, gdb -p still answers ptrace: Operation not permitted., and nothing in the transaction tells you why.
The cause is a kernel setting, kernel.yama.ptrace_scope, which Fedora now leaves at the kernel’s own default of 1. Older releases switched it off on every install, so tutorials and IDE debuggers written for them assume that you can attach to anything you own. This guide follows that one value from the package that should set it, through the tools that trip over it, into containers and toolbox, and ends with the fix that fits each kind of machine. Every capture below was run on a Fedora 45 Cloud VM in October 2026, with gdb 17.2, strace 7.2, py-spy, rootless Podman and toolbox tested against the same sleeping process.
Scope 1 blocks attaching, not debugging
Yama is a small Linux security module that adds one rule on top of the normal permission check. At scope 1, a process may only trace its own descendants, so a debugger that starts the program can trace it, while a debugger that attaches to something already running is refused, even when both run as you. Check what your system uses first:
sysctl kernel.yama.ptrace_scope
A fresh Fedora 45 install reports the restricted mode. No file in /usr/lib/sysctl.d or /etc/sysctl.d sets the key, so this is the kernel’s built-in default showing through. That matters later, because deleting a file never brings this value back on a running system:
kernel.yama.ptrace_scope = 1
Scope 1 is narrower than the error makes it look, because it only stops one operation: attaching to a process that is not your child. The rest of your debugging workflow carries on as before, so check which of these you actually do before you change anything.
gdb ./programandstrace commandstill work, because the tool starts the process itself- core dumps are still written, and gdb still opens them
- root still attaches to anything, since Yama doesn’t restrict
CAP_SYS_PTRACE gdb -p,strace -p, profilers and IDE “attach to process” are refused
If your habit is to start a service, find its PID and attach, you’ll hit the error on the first try. If you launch programs under the debugger instead, you may never notice the change, because the tool is the parent. The same rule also guards reads of /proc/PID/mem and the process_vm_readv syscall, which is why memory profilers fail alongside debuggers.
The error each tool prints
The tools report the same refusal in different words, and only one of them names the setting. To reproduce it, start a target in the background as your normal user:
sleep 3600 &
Then look up its PID, which every attach command below takes:
pgrep -u "$USER" -x sleep
The lab target got PID 7931, so substitute your own number wherever 7931 appears:
7931
Each message below came from attaching to that process, so you can match the message you see against the cause.
gdb: ptrace: Operation not permitted.
gdb gives you the least help. It prints the attach attempt and the raw errno, and nothing that points at the cause:
gdb -q -p 7931 -ex quit
Fedora’s build prints no hint about Yama, only these two lines. In batch mode (-batch) it also exits with status 1, which is what a script sees:
Attaching to process 7931
ptrace: Operation not permitted.
You’ll find the evidence in the kernel log. Yama logs refused attaches with both command lines, rate-limited so a retry loop can drop some, which is useful when an IDE hides the real error behind a generic “failed to attach” dialog:
sudo journalctl -k -g ptrace
Both lab attempts left a line naming the target and the tool that tried:
Oct 22 09:56:37 devbox01 kernel: ptrace attach of "sleep 3600"[7931] was attempted by "gdb -p 7931 -batch -ex bt"[7935]
Oct 22 09:56:45 devbox01 kernel: ptrace attach of "sleep 3600"[7931] was attempted by "gdb -q -p 7931 -ex quit"[7973]
Three different checks return the same errno, so the log tells you which one refused the attach:
- a Yama refusal leaves this kernel line
- an SELinux denial leaves an AVC in the audit log instead
- a cross-namespace refusal, covered under containers below, leaves nothing at all
Check the journal before you change any setting, because installing a package will not fix an SELinux or namespace refusal.

Look at the first two commands in that capture. gdb is installed and the scope is still 1, which is the part most people miss, so the next section explains it.
strace: sysctl kernel.yama.ptrace_scope = 1 may be restricting this attach
strace is more helpful, because it reads the sysctl when an attach fails and tells you about it. Try the same PID:
strace -p 7931
The second line points straight at the setting:
strace: attach: ptrace(PTRACE_SEIZE, 7931): Operation not permitted
strace: sysctl kernel.yama.ptrace_scope = 1 may be restricting this attach
On a host you will normally only see this if strace was installed without its weak dependencies, since a plain dnf install strace fixes the scope on the way in. You will still see it inside containers and on hosts where a drop-in pins the scope at 1, both covered below.
py-spy: Permission Denied
Tools installed outside dnf don’t pull in any fix, because nothing in their install touches the host’s sysctl files. py-spy, installed with sudo dnf install uv and then uv tool install py-spy, shows the case you’ll meet most with language profilers. The target was a tiny worker, saved as ~/app/worker.py:
import time
def worker():
while True:
time.sleep(5)
worker()
The py-spy release tested can’t read the system python3 on Fedora 45 at all: even with sudo it stopped at Error: Unsupported version of Python: 3.15.0. So the worker ran on a uv-managed interpreter. uv python install 3.13 puts a python3.13 launcher in ~/.local/bin, and running it directly keeps $! pointing at Python rather than at a wrapper:
python3.13 ~/app/worker.py &
Point py-spy at that process, PID 1869 in the lab, as the same user:
py-spy dump --pid 1869
Its advice is to rerun with sudo, which works, though it is not the only option:
Permission Denied: Try running again with elevated permissions by going 'sudo env "PATH=$PATH" !!'
Anything else that attaches to a PID and comes from pip, cargo, go install or a source build is in the same position, because no Fedora package pulled the fix in on its behalf. If your tool didn’t come from a package that recommends it, assume it’ll fail on a fresh install.
The gdb package never asks for the fix
Fedora did not make ptrace stricter by changing the kernel. It stopped shipping the file that relaxed it. Up to Fedora 44, a subpackage called elfutils-default-yama-scope came in on every system as a dependency of elfutils-libs and set the scope to 0. In Fedora 45 that subpackage is renamed yama-ptrace-enable, elfutils-libs no longer requires it, and debugger packages are meant to pull it in as a weak dependency instead. The Restrict ptrace by default change page lists gdb among them.
The repository disagrees. Ask dnf which packages actually recommend it:
dnf repoquery --whatrecommends yama-ptrace-enable --qf "%{name}\n" | sort -u
Six packages carry the recommendation. gdb is not one of them, and neither is ltrace, which the change page also lists:
delve
elfutils
lldb
lldb22
strace
systemtap-runtime
Why does gdb miss out? Its real binary lives in gdb-headless, and that package still recommends the old capability name, default-yama-scope:
rpm -q --recommends gdb-headless
The first line is the stale name:
default-yama-scope
python3-rpm
Nothing in Fedora 45 provides default-yama-scope any more, since the new package only provides its own name. dnf skips an unresolvable weak dependency without a word. On the lab VM, sudo dnf install gdb brought in eleven packages and left the scope at 1, so three situations leave you at scope 1 with a debugger installed:
- you installed gdb or ltrace, or a tool from pip, cargo, go or a tarball
- you installed strace or elfutils with
install_weak_deps=False, which minimal and container builds often set - you upgraded from Fedora 44 without any of the recommending packages, because
elfutils-libsnow obsoletes the old subpackage and removes it
The Fedora 45 changes post confirms the upgrade case on a real 44 to 45 upgrade, where the scope came up as 1 after the reboot. That is the machine where gdb worked yesterday and fails today.
One package turns attaching back on
The Fedora way to restore the old behaviour is the package the debuggers were supposed to pull in. It contains a single sysctl file and nothing else:
sudo dnf install yama-ptrace-enable
You don’t need a reboot, because systemd ships an RPM file trigger that runs /usr/lib/systemd/systemd-sysctl whenever a package drops a file into /usr/lib/sysctl.d, so the value changes during the transaction. Read it back:
sysctl kernel.yama.ptrace_scope
It now reads 0. The value comes from /usr/lib/sysctl.d/10-default-yama-scope.conf, the only file the package installs, so that file is what you look for when another machine behaves differently:
kernel.yama.ptrace_scope = 0
The same gdb -p 7931 that failed earlier attached at once and listed the inferior as the running /usr/bin/sleep, because the scope is the only thing that changed. Removing the package doesn’t undo it, though. After sudo dnf remove yama-ptrace-enable the file was gone and the scope still read 0, and even restarting systemd-sysctl left it there, because a missing file sets nothing. It drops back to 1 on the next boot, or straight away if you write the value yourself:
sudo sysctl -w kernel.yama.ptrace_scope=1
On a Workstation install where you debug every day, this package is the right fix. It’s one line in a provisioning script for developer machines, next to the development tools group that brings the compilers in.
Containers stop at the namespace boundary
The scope is a host setting, so a container inherits it and can’t change it. That surprises you in two different ways depending on the tool:
- rootless Podman blocks attaching inside the container
- toolbox ignores the fix you install inside it
Both showed up in the lab with rootless Podman, and each needs a different answer.
Rootless Podman needs SYS_PTRACE
Inside a container, the debugger and its target are usually not parent and child, because the entrypoint shell starts both. The stock Fedora image, with strace installed at start, shows the difference a single capability makes:
podman run --rm --cap-add=SYS_PTRACE registry.fedoraproject.org/fedora:45 bash -c "dnf -yq install strace >/dev/null; sleep 300 & sleep 1; timeout 2 strace -p \$! -e trace=none"
Without the flag the attach failed with the same Yama hint as on the host. With it, strace attached and detached cleanly once dnf’s progress lines had scrolled past:
strace: Process 97 attached
strace: Process 97 detached
Rootless Podman’s default capability set doesn’t include SYS_PTRACE, so add it to your debug run only, not to the image or the production unit, since a service rarely needs to trace anything. The capability holds inside the container’s user namespace, so it lets you trace processes in that container and nothing on the host. The Podman on Fedora guide covers the rest of the rootless setup.
Toolbox ignores the package you install inside it
On Silverblue and other Atomic desktops, your debuggers usually live in a toolbox, which is where the wrong fix is easiest to apply. Installing strace inside the toolbox happily pulled yama-ptrace-enable in with it, and the attach still failed:

The package only writes a file into the container’s own /usr/lib/sysctl.d, where no systemd-sysctl runs against the host kernel. Writing the key by hand with sudo inside the toolbox fails as well, because the key belongs to the host and Yama only accepts writes from a process holding CAP_SYS_PTRACE in the host’s own namespace. The host-wide scope has to change on the host, and on an Atomic system you don’t need to layer a package for it. The drop-in described in the next section works, because /etc stays writable there.
Inside the toolbox, sudo is the quicker answer. Toolbox containers run privileged, so root in the toolbox holds CAP_SYS_PTRACE over the toolbox’s own namespace, and with the host still at scope 1, sudo strace -p attached to a process started in the same toolbox:
strace: Process 1325 attached
strace: Process 1325 detached
What a toolbox can’t do is attach to a process running on the host. After the host was switched to 0 and rebooted, strace in the toolbox attached to a process started inside the toolbox, and still got a bare EPERM against a host process. sudo inside the toolbox, tried with the host at scope 1, got the same bare refusal:
strace: attach: ptrace(PTRACE_SEIZE, 917): Operation not permitted
This time you get no Yama hint, no kernel log line and no AVC. The toolbox runs in a user namespace, and the kernel’s capability check refuses a tracer from a child namespace unless it holds CAP_SYS_PTRACE over the target’s namespace, which a rootless container never does. So run the program you’re debugging inside the same toolbox, or attach from the host. The toolbox and Distrobox guide shows how the container shares your home directory, which makes the first option easy.
Keep scope 1 and attach anyway
You don’t have to give up the restriction to debug. Scope 1 exists so that a compromised app or a malicious build script can’t read the memory of your password manager or SSH agent, and for a laptop that only debugs now and then, keeping it is reasonable. Two ways around it leave the default alone:
- sudo, for an occasional attach
- an opt-in from the program you want to trace
Pick sudo when the attach is rare, and the opt-in when you profile the same service all day.
Root holds CAP_SYS_PTRACE and Yama lets it through. With the scope at 1, sudo gdb -p 7931 attached and sudo env PATH=$PATH py-spy dump --pid 1869 printed the Python stack:
Process 1869: python3.13 /home/kenneth/app/worker.py
Python v3.13.15 (/home/kenneth/.local/share/uv/python/cpython-3.13.15-linux-x86_64-gnu/bin/python3.13)
Thread 1869 (idle)
worker (worker.py:5)
<module> (worker.py:7)
For the opt-in, a process calls prctl(PR_SET_PTRACER, ...) to name a tracer it will accept, or PR_SET_PTRACER_ANY to accept any process of the same user, and Yama honours that at scope 1. That suits a long-running dev server you want to profile without sudo. Create a test file in your editor:
nano ~/app/optin.py
This is the exact script that ran in the lab. The constant is the value of PR_SET_PTRACER from the kernel headers:
import ctypes, time
PR_SET_PTRACER = 0x59616d61
PR_SET_PTRACER_ANY = ctypes.c_ulong(-1)
libc = ctypes.CDLL(None, use_errno=True)
libc.prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0)
while True:
time.sleep(5)
Start it with python3 ~/app/optin.py &. With the scope still at 1, a plain gdb -p attached and showed the interpreter as the inferior:
0x00007f09b8f3bc5b in clock_nanosleep@GLIBC_2.2.5 () from /lib64/libc.so.6
Num Description Connection Executable
* 1 process 8422 1 (native) /usr/bin/python3.15
strace attached to the same PID too. Only the process that made the call is opened up, so the rest of your session stays protected. Keep the call behind a debug flag in real code, because any process you own could then read that program’s memory.
An /etc drop-in outranks the package
A file you put in /etc/sysctl.d lasts whatever packages come and go, in either direction, because of how systemd-sysctl orders its input. In order:
- it reads
/etc/sysctl.d,/run/sysctl.d,/usr/local/lib/sysctl.dand/usr/lib/sysctl.d - a file with the same name in an earlier directory replaces the later one, so
/etcoverrides a vendor file outright - the rest are sorted by file name across all the directories
- they are applied in that order, so the last file to set a key wins
Pick a number higher than 10 so your file sorts after the vendor one, and lower than any 99 file you already have. Open a new drop-in:
sudo nano /etc/sysctl.d/60-ptrace-scope.conf
For a developer box or an Atomic desktop that should always allow attach, add one line:
kernel.yama.ptrace_scope = 0
Apply it now with sudo sysctl --system, or wait for the next boot. The lab VM came back from a reboot at 0 with only this file setting the key, so the package isn’t needed at all on a machine that has the drop-in.
What about a machine that must stay strict? The same drop-in keeps a hardened host at 1, even after someone installs strace and drags yama-ptrace-enable in, because your file sorts after the vendor one. With the file changed to kernel.yama.ptrace_scope = 1, installing the package afterwards left the value at 1:

The binary isn’t on your PATH, so call it as /usr/lib/systemd/systemd-sysctl --cat-config. It prints each file under a # /path header in the order it applies them. The vendor file 10-default-yama-scope.conf sets 0 first, and 60-ptrace-scope.conf sorts after it, so its 1 is the value that sticks.
Check it whenever a value you set doesn’t hold, because a later file wins over yours. Two later files are common on Fedora:
99-sysctl.conf, a link to/etc/sysctl.confthat ships on every install99-cfg-hardening.conf, if you followed the Fedora hardening guide, which pins the scope at 1
If either sets the key, your 60 file never wins, so change the value there or remove the line.
Two stricter values exist, and the lab showed why most machines should not use them. Both stop more than you’d expect, and the second cannot be taken back without a reboot.
- 2: only processes with
CAP_SYS_PTRACEmay trace. sudo strace still attached, but strace’s own startup check failed for the user withPTRACE_TRACEME: Operation not permitted, since the tracer now needs the capability even for its own child - 3: no process may trace at all, root included. Setting it back failed with
sysctl: setting key "kernel.yama.ptrace_scope": Invalid argument, and only a reboot cleared it
Scope 3 belongs on a locked-down appliance where nobody will ever need gdb. If you put it in a drop-in, every boot locks it again, so remove the file before you reboot to get out of it. SELinux has its own switch on top of whatever Yama allows, the deny_ptrace boolean, which blocks ptrace for every process, unconfined ones included. The hardening guide above covers it, and the SELinux survival guide shows how to read the AVCs it leaves.
Which fix fits which machine
Each fix changes the setting at a different layer, so the right one depends on who should be able to attach and whether your system is mutable. From the narrowest exception to the system-wide settings:
| Situation | Fix | Scope after | Survives reboot |
|---|---|---|---|
| Occasional attach on a laptop | sudo gdb -p PID | 1 | Nothing to persist |
| One dev service you profile often | prctl(PR_SET_PTRACER, ...) in the program | 1 | Per process |
| Container debug session | podman run --cap-add=SYS_PTRACE | host value | Per container |
| Workstation used for daily debugging | sudo dnf install yama-ptrace-enable | 0 | Yes, while installed |
| Silverblue or Atomic with toolbox | /etc/sysctl.d/60-ptrace-scope.conf set to 0 | 0 | Yes |
| Hardened host that must stay strict | Same drop-in set to 1 | 1, even with the package | Yes |
Whichever you pick, check the result with sysctl kernel.yama.ptrace_scope and /usr/lib/systemd/systemd-sysctl --cat-config rather than trusting the package list, because on Fedora 45 a debugger being installed tells you nothing about the scope. For the rest of the release’s behaviour changes that break old habits, the Fedora 45 post-install checklist lists them alongside the fixes.