Linux kernel deleted strncpy across 360 patches
Linux removed strncpy across 360 patches over six years. The exposure: a bounded write primitive used as a safety control it never implemented.
- Opening Claim
The Linux kernel removed the strncpy API. The reported cost of that removal is six years of work and 360 patches. That is the entire confirmed fact set, and it is enough to define the position.
The number that matters is 360. Each patch converts a call site away from a function the project judged unsafe enough to retire rather than document. A six-year, 360-patch removal is not housekeeping. It is the deliberate withdrawal of a primitive whose contract could not be enforced safely at scale. When a maintainer team spends that long deleting one function instead of wrapping or guarding it, the decision is the signal. They removed the ability to make the unsafe choice.
Separate what is confirmed from what is being implied around it. Confirmed: the function was eliminated, the effort spanned six years, the change set was 360 patches. Not confirmed: that any of those 360 sites was exploited, the number of incidents, any dwell time, any breach. 360 patches measure exposure surface. They do not measure compromise. The framing that this represents six years of quietly exploited vulnerabilities is not supported by the stated facts and is treated here as not confirmed. Absence of breach data is a condition, not a gap to fill with assumed attacker behaviour.
- The Original Assumption
The assumption embedded in 360 call sites was that strncpy is a bounded copy and therefore a safe copy. The presence of the length parameter created the appearance of a guard. A developer reading strncpy(dest, src, n) sees a number that caps the write and reads it as a safety boundary. That reading is the failure. The bound governs how many bytes are written. It says nothing about whether the result is a valid string.
Define what the function actually does, because the gap between the two behaviours is the whole exposure. If the source length is greater than or equal to n, strncpy writes n bytes and does not write a null terminator. The result is a buffer that is not terminated. Any downstream code that treats that buffer as a C string reads forward until it encounters a null byte somewhere else in memory. That is an over-read. It produces information disclosure, corrupted length calculations, or a crash, depending on what sits past the boundary. The second behaviour: if the source is shorter than n, strncpy pads the remaining space with null bytes up to n. The bound describes exactly how many bytes are touched. It never describes a safe, terminated string.
This is a control-identity problem at the API surface. strncpy was used as a safety control across the tree. It was never a safety control. It is a bounded write primitive, and termination is outside its contract. The property the call sites relied on, a null-terminated result, was assumed, not guaranteed. A control that does not enforce the property you depend on is not a control. The reliance was on termination. strncpy never promised termination. The boundary that broke is the boundary between a length-bounded write and a safe string, and the codebase treated them as the same thing.
- What Changed
The project did not wrap strncpy, annotate it, or restrict it by policy. It eliminated the function across the tree over 360 patches. Operationally, that means the unsafe contract can no longer be selected at any call site, because the call no longer exists. The change is structural. It removes the failure mode at the source rather than guarding each instance of it.
The specific replacement API is not stated in the provided facts and is treated as not confirmed. What is confirmed is the direction of the change: removal, not mitigation. That direction is the operative detail. Mitigation leaves the dangerous primitive in place and depends on every future author and reviewer choosing correctly at every call site. Removal changes the set of available choices. The maintainers chose to constrain the option space instead of trusting discipline to hold across the codebase.
The 360 figure is the evidence for why guidance was rejected. 360 sites is what accumulates when an unsafe default is available and humans are asked to use it carefully. If a system allows an unsafe call, the call will appear. Documentation and review operate on human judgment at every line. Elimination operates on the API itself, which is enforcement and not advice. The six-year cost is the second lesson in the same fact: a single bad-by-default primitive required six years and 360 changes to retire. The cost of removing an unsafe default scales with how widely it was adopted before anyone moved to remove it. The longer it stays the default, the larger the removal bill.
- Mechanism of Failure or Drift
The failure mechanism is replication of an unenforced assumption across independent call sites. strncpy exposes a length parameter. That parameter bounds the byte count written. It does not govern termination. Every call site that read the bound as a safety boundary inherited the same defect, because there is no central enforcement point for termination anywhere in the contract. The terminated-string property is decided locally, at each call, by the author writing it. That is the mechanism by which one primitive becomes 360 instances of one exposure. Not a function with 360 bugs. One unenforced property, restated 360 times.
The drift is the accumulation, and 360 is its measurement. An unsafe default that remains selectable is selected wherever it appears adequate. The behavior is conditional on input. If the source is shorter than n, strncpy pads to n with null bytes and produces a terminated result, which is logically the working case. The defective behavior surfaces only when the source length is greater than or equal to n, where no terminator is written. A call site can pass compilation, pass review, and produce correct output for every input that stays under the boundary. The defect is present at write time. The observable failure is gated behind a specific input condition. That latency between writing the unsafe call and reaching the unsafe behavior is what lets the count grow without any visible signal at the call site.
This is why guarding does not address the mechanism. When the safe property is decided per call site, every mitigation built on documentation or review depends on every author and every reviewer evaluating termination correctly, at every line, on every change. The count reached 360 precisely because that judgment did not hold across the tree. Guidance operates on discipline at human scale. The mechanism operates on availability at code scale. Removing the function moves the decision from per-call-site judgment to a single structural fact: the unsafe contract is no longer selectable. That is the only intervention that acts on the mechanism instead of on its instances.
- Expansion into Parallel Pattern
The pattern is the gap between what an interface enforces and what its callers depend on, replicated wherever that interface is the available default. strncpy is one instance of it. The general form: an API accepts a parameter that constrains a measurable quantity, the byte count, and callers read that constraint as a guarantee of an invariant the API never governs, the terminated string. The presence of the parameter is the source of the misread. A number that caps the write is treated as a number that makes the result safe. The interface enforces the weaker property. The reliance is on the stronger one. The two are conflated at the call site because nothing in the interface distinguishes them.
Any primitive that succeeds on the common input and fails only on the boundary input accumulates call sites the same way. The bound confines the failure to a conditional case. The conditional case is reached rarely enough that the call site reads as correct under normal traffic. Correct-on-common-input is not the same property as correct-on-all-input, and the interface returns no signal that separates them. Each call site that depends on the stronger property while the interface delivers only the weaker one is an independent instance of the same defect, governed by no shared control. The defect does not propagate through a single path that can be patched once. It is instantiated wherever the interface is called.
The structural conclusion follows directly from the mechanism. Wherever the safe property is decided at the call site rather than enforced by the interface, the number of unsafe call sites is bounded only by how widely that interface is used. The pattern is not specific to string copy. It is specific to any interface whose contract is narrower than the assumption its callers attach to it, and whose unsafe form is the default form. The 360 figure is what this mechanism produces at the scale of one kernel and one function. The same mechanism at any interface produces the same accumulation, sized to that interface’s adoption. The narrower the gap between contract and assumption, the longer it stays invisible. The wider the adoption, the larger the eventual count.
- Hard Closing Truth
strncpy was used as a safety control across the tree. It enforced a byte bound. It did not enforce termination. A control that does not enforce the property you depend on is not a control. 360 call sites depended on a property the primitive never provided, which is the confirmed exposure: 360 sites built on an unenforced assumption. Not confirmed, and held as not confirmed: that any of those sites was exploited, that any breach occurred, that any dwell time existed. The exposure is structural and counted. The compromise is not in the facts and is not asserted here.
If a system makes an unsafe call available, the call appears. 360 is the proof at scale. Documentation, guidance, and review act on human judgment at every line, and across this tree that judgment did not hold, which is why the count reached 360 and why removal was chosen over guarding. Enforcement is a property of the interface. Discipline is a property of the author. The maintainers did not ask call sites to behave. They removed the ability to make the unsafe choice. That distinction is the entire difference between a control and advice, and it is the only reading of this change the facts support.
The six-year, 360-patch removal is the bill for shipping an unsafe default and leaving it the default. The cost of withdrawing a bad primitive scales with how widely it was adopted before anyone moved to retire it. The operative requirement is not that strncpy was unsafe. It is that the unsafe form existed, was selectable, and was the path of least resistance, and the only intervention that closed it was deleting the option. When a primitive is treated as a control it does not implement, the boundary is already broken at the point of the assumption, not at the point of failure. The fix that scales is not better usage. It is removing the primitive.
See also: NordVPN for tunneled traffic when operating outside controlled networks.
#ad Contains an affiliate link.
Keep Reading
legacy systems1992 hardware, no MMU, every payload lands
The Game Boy Work Boy exposes a system with no MMU, DEP, or ASLR - flat executable memory and a fixed layout where any write becomes code execution.
linux kernelCVSS 5.5 is lying to you
A nine-year-old Linux kernel flaw enables root command execution. CVSS 5.5 understates the outcome. Patch scope and operator action.
C programmingHow GCC 4.3 deleted a NULL check in 2009
How undefined behavior in C lets compilers delete safety checks, why it drives most memory-safety CVEs, and what it means for AI-generated code.
Stay in the loop
New writing delivered when it's ready. No schedule, no spam.