70 Developers, 362 Patches, 6 Years: How Linux Finally Removed strncpy()
One function.
Six years.
Seventy contributors.
Three hundred and sixty-two patches.
That sounds excessive until you understand what the function was doing, how deeply it was embedded, and what could happen if even one replacement was wrong.
The function was strncpy().
It had existed in Unix and C libraries since the late 1970s. It was used throughout the Linux kernel for decades. It appeared in drivers, networking code, storage systems, architecture-specific implementations, and low-level components running on millions of servers, phones, routers, embedded devices, and cloud machines.
At first glance, removing it looked simple.
Find every call.
Replace it with a safer function.
Compile the kernel.
Ship the patch.
That is not what happened.
Linux developers had to inspect every use individually because strncpy() had been used to perform several different jobs. Replacing all of those calls with one modern alternative could silently corrupt data, change network packets, break hardware drivers, or create a new security vulnerability.
The cleanup became a six-year engineering campaign.
This is the story of why one old function became so dangerous, why automation could not safely replace it, and what the project teaches us about legacy systems, secure development, and the growing role of AI in software security.
The Function That Looked Safer Than It Was
Most C programmers learn about strcpy() early.
The function copies a string from one memory location to another:
strcpy(destination, source);
The problem is that strcpy() does not know how large the destination buffer is.
If the source string is larger than the available space, the function keeps writing beyond the end of the buffer.
That can overwrite nearby memory.
The result may be:
- application crashes
- corrupted data
- arbitrary code execution
- privilege escalation
- security vulnerabilities
Then developers discover strncpy().
Its name suggests that it is the safer, size-limited version:
strncpy(destination, source, destination_size);
It accepts a maximum number of bytes to copy, which sounds like the solution.
But the function has behavior that regularly surprises developers.
If the source string is as long as or longer than the supplied limit, strncpy() may not add the terminating null byte.
That small detail can change everything.
Why the Null Byte Matters
C strings do not store their own length.
Instead, a string is represented as a sequence of bytes ending with a special zero byte:
H e l l o \0
The \0 byte tells the program where the text ends.
Without it, functions that read the string may continue reading memory until they eventually find a zero somewhere else.
Consider this code:
char destination[5];
strncpy(destination, "Hello", sizeof(destination));
The word Hello requires six bytes:
- five bytes for the letters
- one byte for
\0
The destination has only five bytes.
The result may look like this:
H e l l o
There is no terminating zero.
Now imagine another function tries to print or measure the string:
printf("%s\n", destination);
The program may continue reading beyond the five-byte buffer.
In user-space software, that may expose nearby process memory or cause a crash.
Inside the kernel, the consequences can be more serious because kernel memory may contain:
- credentials
- cryptographic material
- file contents
- process information
- network data
- security-sensitive metadata
A missing byte can become a memory disclosure.
The Dangerous Illusion of Safety
strncpy() became widely used partly because it looked responsible.
A developer reviewing code might see a length argument and assume the buffer was protected.
That assumption is only partly correct.
The function limits the number of bytes written, but it does not guarantee that the result is a valid null-terminated string.
This creates an especially dangerous category of bug.
The code looks safer than the alternative.
That can make it harder to notice during review.
The Linux kernel documentation eventually classified strncpy() as deprecated and warned that it could produce non-null-terminated strings, leading to read overflows and other unexpected behavior.
The solution sounded straightforward:
Replace every use of
strncpy().
The real difficulty was discovering what each use was supposed to mean.
One Function Had Become Five Different Operations
Over several decades, developers had used strncpy() for multiple purposes.
Some callers wanted to copy a normal C string.
Others wanted to fill a fixed-width field with zeros.
Some were converting a fixed-size byte array into a string.
Others were preparing exact binary layouts for network protocols or hardware interfaces.
The same function call could represent completely different intentions.
That meant there was no universal replacement.
1. Copying a normal null-terminated string
A typical developer may want to copy text into a buffer and guarantee that the result ends safely.
A modern replacement may be:
strscpy(destination, source, sizeof(destination));
strscpy() is designed for standard string-copy behavior and ensures predictable termination when the buffer size is valid.
2. Copying a string and padding unused bytes
Some structures require a field to occupy a fixed number of bytes.
If the string is shorter than the field, the remaining space must be filled with zeros.
For that case, the appropriate replacement may be:
strscpy_pad(destination, source, sizeof(destination));
This is not the same as a normal string copy.
The padding may be required by a binary format, kernel structure, or external interface.
3. Converting a fixed-size byte field into a C string
Some hardware, network, and file formats store text in fixed-width byte arrays that may not contain a null terminator.
To turn that field into a safe C string, the kernel can use a helper such as:
memtostr(destination, source);
The operation is conceptually different from copying one string to another.
The source may not be a C string at all.
4. Writing a string into a raw memory field
The reverse operation also exists.
A driver or protocol may require a string to be placed into a fixed-size byte field without treating that field as a normal C string.
A suitable helper may be:
strtomem(destination, source);
Or, when padding is required:
strtomem_pad(destination, source, padding_byte);
Adding a null terminator where the format does not expect one could change the exact bytes transmitted to hardware or across a network.
5. Copying and padding binary data
Some locations needed exact memory-copy behavior combined with padding.
A helper such as:
memcpy_and_pad(destination, destination_size,
source, source_size, padding_byte);
may express the intention more accurately.
This distinction matters because the kernel is full of fixed-format structures.
The bytes may be consumed by:
- a network device
- a storage controller
- firmware
- a filesystem
- another machine
- a userspace application
- a hardware protocol
One extra zero byte can be a compatibility bug.
One missing zero byte can be a security bug.
Why a Global Search and Replace Would Have Failed
Imagine running a script like this:
Replace every call to strncpy() with strscpy()
Some code would improve.
Other code would break.
A call that was supposed to pad a field could stop padding it.
A raw memory field could be treated as a C string.
A protocol packet could change length or contents.
A structure shared with firmware could become incompatible.
A Wi-Fi network identifier or device field could be rewritten incorrectly.
The kernel might still compile.
Many affected systems might still boot.
The failure could appear only on:
- one hardware model
- one network protocol
- one architecture
- one unusual input
- one driver used by a small number of systems
- one production workload months later
That is the most dangerous type of migration bug.
The code passes ordinary tests but violates an assumption somewhere else.
Every call site therefore had to answer a deeper question:
What was the original programmer trying to achieve?
That question cannot be answered reliably by syntax alone.
It requires context.
Seventy Contributors and No Shortcut
The removal effort took place through the Linux Kernel Self Protection Project, commonly known as KSPP.
According to project maintainer Kees Cook, the final cleanup represented:
- 362 commits
- 70 contributors
- approximately six years of work
One contributor, Justin Stitt, reportedly authored more than 200 of those commits.
That means hundreds of separate decisions.
Each patch needed to identify:
- the type of the source
- the type and size of the destination
- whether null termination was required
- whether zero padding was required
- whether the destination was a raw byte array
- whether the data crossed a kernel boundary
- whether the data was part of a hardware or network format
- whether tests covered the behavior
- whether architecture-specific code behaved differently
The difficult part was not typing the replacement.
The difficult part was understanding the intention behind code written across decades.
Why Six Years Was Not Slow
Modern software culture celebrates speed.
Teams ship daily.
Startups advertise features built in a weekend.
AI tools can generate complete applications in minutes.
Against that backdrop, six years to remove one function may sound inefficient.
It was not.
The Linux kernel is not an ordinary application.
A mistake can affect:
- data centers
- Android devices
- network infrastructure
- embedded systems
- industrial systems
- medical equipment
- cloud platforms
- developer machines
The kernel must support countless hardware combinations and old interfaces.
A patch that appears correct on one developer's laptop may behave differently on another architecture or device.
The cost of rushing is enormous.
The project was slow because the work demanded precision.
That is a form of engineering maturity.
The Best Fix Was Removing the Dangerous Option
The campaign did not end after every known call site was updated.
The developers removed strncpy() from the kernel itself.
That step changed the outcome from:
Developers should avoid this function.
to:
Developers cannot use this function here.
That is a much stronger security control.
Documentation can be ignored.
Reviewers can miss things.
Developers can repeat old habits.
A build failure is harder to ignore.
If a future patch attempts to call strncpy(), the kernel will not simply accept it and rely on someone to notice.
The code will fail to build.
This is an important security principle:
The safest dangerous behavior is the behavior the system makes impossible.
Warnings Are Weaker Than Guardrails
Many engineering teams rely heavily on documentation.
They create style guides that say:
- do not use this function
- do not store secrets here
- do not bypass this check
- do not deploy from a local machine
- do not log sensitive information
Those rules help, but they are fragile.
People forget.
New employees may not read the document.
AI-generated code may use familiar but unsafe patterns.
Deadlines create pressure.
Code review is imperfect.
Better systems turn guidance into enforcement.
Examples include:
- compiler errors
- static-analysis rules
- dependency policies
- permission boundaries
- type systems
- automated tests
- branch protection
- secret scanning
- deployment controls
- API schema validation
The Linux project did not merely discourage strncpy().
It removed the possibility of using it.
What This Teaches Us About Legacy Code
Legacy code is often described as old code.
That definition is incomplete.
Legacy code is code that contains assumptions no one fully remembers.
A function may appear simple while carrying decades of hidden meaning.
The danger is not always poor quality.
The danger is invisible context.
Consider a field copied with strncpy() in a network driver.
Why was it padded?
Why was the buffer exactly 32 bytes?
Did firmware require that format?
Was a missing terminator intentional?
Was the behavior copied from another operating system?
Would changing it affect compatibility with older devices?
The code may not answer those questions directly.
Developers must reconstruct the contract from surrounding logic, specifications, commit history, tests, and hardware behavior.
That is why legacy modernization is rarely a simple rewrite.
A rewrite can remove visible code while accidentally removing invisible knowledge.
What This Teaches Us About AI-Generated Refactoring
An AI coding assistant can find every occurrence of strncpy() instantly.
It can suggest replacements.
It can generate patches.
It can explain the differences between strscpy(), memtostr(), and strtomem().
That is useful.
But selecting the correct replacement may require understanding facts that are not obvious in the local code.
An AI model may need to know:
- whether the destination is part of a protocol
- whether external hardware reads the field
- whether exact padding is required
- whether callers expect truncation
- whether the source is guaranteed to contain a terminator
- whether an architecture-specific implementation behaves differently
- whether a subtle compatibility contract exists
The model can assist with the analysis.
It should not be trusted to make every migration decision without review.
This is especially true in security-sensitive code.
AI is excellent at scale
AI can help teams:
- identify risky patterns
- classify call sites
- generate candidate patches
- summarize surrounding code
- create tests
- compare APIs
- search documentation
- detect inconsistent migrations
Humans remain essential for intent
Engineers still need to verify:
- semantic correctness
- compatibility requirements
- undocumented assumptions
- hardware behavior
- security impact
- operational risk
The Linux cleanup is a good example of how AI and human judgment should work together.
AI can reduce the cost of investigation.
Humans must own the decision.
Security Work Is Often Repetitive and Invisible
A new feature is easy to demonstrate.
A security cleanup may produce no visible change for users.
The system behaves the same before and after the patch.
That is the goal.
The difference is that one category of failure is no longer possible.
This type of work rarely receives the same attention as a product launch.
Yet it may protect more users than many visible features.
Hundreds of commits to remove one unsafe function represent:
- reading old code
- checking data structures
- reviewing driver behavior
- testing uncommon paths
- discussing semantics
- responding to review comments
- revising patches
- waiting for subsystem maintainers
- handling architecture differences
There is no dramatic final interface.
The success is the absence of future bugs.
The Bigger Story: Memory Safety Still Matters
The strncpy() story is part of a broader movement away from memory-unsafe programming patterns.
Languages such as C and C++ give developers direct control over memory.
That control enables extraordinary performance and low-level access.
It also creates classes of vulnerabilities involving:
- buffer overflows
- use-after-free errors
- out-of-bounds reads
- out-of-bounds writes
- double frees
- uninitialized memory
- integer overflows affecting allocation sizes
- missing string terminators
Operating systems and infrastructure cannot move away from C overnight.
The existing codebase is enormous.
Hardware interfaces, performance requirements, and compatibility obligations make migration difficult.
That means security progress often comes through incremental improvements:
- safer helper functions
- compiler protections
- sanitizers
- fuzz testing
- static analysis
- control-flow protections
- memory-safe components
- restricted APIs
- automated checks
Removing strncpy() is one such improvement.
It does not make the entire kernel memory safe.
It removes one recurring source of ambiguity and risk.
The Timing Matters in the Age of AI
The final removal arrived during a period when AI systems were becoming increasingly capable of finding, explaining, and exploiting software vulnerabilities.
That changes the security timeline.
Traditionally, a public patch might reveal enough information for skilled researchers to understand the original vulnerability.
Creating a reliable exploit still required time and expertise.
AI can accelerate parts of that process.
A model may:
- compare the vulnerable and patched code
- identify the security-sensitive change
- generate test inputs
- explain the memory corruption
- produce a proof of concept
- adapt known exploit techniques
This creates pressure on defenders.
Once a security patch becomes public, attackers may be able to understand it faster than many organizations can deploy it.
The problem is sometimes called patch diffing.
The attacker studies the difference between two versions and works backward to find the bug.
AI makes that analysis faster.
Openness and Coordinated Disclosure
Open-source development depends on transparency.
Public code allows:
- broad review
- independent research
- community contribution
- reproducible builds
- shared learning
- public accountability
Security fixes, however, sometimes require a temporary period of confidentiality.
If a critical vulnerability affects widely deployed software, maintainers may coordinate with major distributors and infrastructure providers before publishing full details.
The purpose is to give key systems time to prepare and deploy updates.
That creates a difficult balance.
Too much secrecy limits independent review and may favor large organizations.
Too much immediate disclosure can give attackers a blueprint before defenders are ready.
AI makes the balance more difficult because the time between patch publication and exploit development may continue shrinking.
The solution cannot be permanent secrecy.
It must be responsible coordination with clear timelines, broad representation, and eventual public disclosure.
Five Lessons Every Development Team Can Apply
You do not need to maintain an operating-system kernel to learn from this project.
1. Replace ambiguous APIs with intention-specific APIs
An API that performs several subtly different jobs invites misuse.
Prefer functions and interfaces that clearly express what the developer intends.
Compare:
strncpy(...)
with:
strscpy(...)
strscpy_pad(...)
memtostr(...)
strtomem(...)
memcpy_and_pad(...)
The second group is more verbose.
It is also clearer.
Clarity is a security feature.
2. Prevent unsafe patterns at build time
Do not rely only on documentation.
Use tools that reject dangerous behavior:
- compiler warnings treated as errors
- linters
- static-analysis rules
- forbidden-function lists
- dependency policies
- secret scanning
- schema validation
- automated security tests
A blocked mistake is cheaper than a reviewed mistake.
3. Do not automate migrations without understanding semantics
Large-scale search and replace is safe only when the old and new operations are truly equivalent.
Before automating a migration, classify the use cases.
Ask:
- What behavior does each caller depend on?
- Are there hidden side effects?
- Is binary compatibility involved?
- Does the data cross a system boundary?
- Do tests verify the important behavior?
Automation should follow understanding.
It should not replace it.
4. Reward long-term maintenance
Organizations often reward visible delivery and underinvest in cleanup.
That creates security debt.
Teams need time for:
- dependency upgrades
- deprecated API removal
- permission cleanup
- test improvement
- documentation
- observability
- incident preparation
- performance maintenance
Six years of incremental work may prevent years of future vulnerabilities.
Maintenance is product work.
5. Use AI as an accelerator, not an authority
AI can help engineers search, classify, explain, test, and review.
It should not be the final decision-maker for security-sensitive migrations.
The correct workflow is:
- Let AI accelerate discovery
- Let AI propose candidates
- Test the behavior
- Review the context
- Let a qualified engineer approve the change
- Enforce the safer pattern automatically
AI can make expert judgment more scalable.
It does not make judgment unnecessary.
A Small Function Can Carry a Huge System Risk
The most striking part of this story is not that strncpy() was unsafe.
Many developers already knew that.
The striking part is how difficult it was to remove safely.
One function had accumulated decades of assumptions across millions of lines of code.
Its name concealed several different intentions.
Its replacements required human interpretation.
Its removal demanded persistence from dozens of contributors.
Then, after every call site was fixed, the kernel eliminated the function entirely so the same mistake could not return.
That is what mature security engineering looks like.
It is not one brilliant patch.
It is a long sequence of careful decisions that steadily makes the system harder to misuse.
How Techifive Approaches Secure Software Development
At Techifive, we build modern web applications, APIs, cloud systems, and AI automation solutions with security and maintainability considered from the beginning.
That means paying attention not only to what software does today, but also to how safely it can evolve tomorrow.
Our work includes:
- secure web application architecture
- API development and integration
- authentication and authorization
- cloud and DevOps infrastructure
- AI automation with controlled permissions
- codebase modernization
- performance optimization
- monitoring and ongoing support
Good software is not only fast to launch.
It should also be clear to maintain, difficult to misuse, and prepared to grow.
To discuss a web platform, software modernization project, secure API, cloud deployment, or AI automation system, visit techifive.com or email support@techifive.com.
Final Thought
The Linux kernel did not become safer because someone found a magical replacement.
It became safer because seventy contributors were willing to study one call at a time.
They read the context.
They identified the real intention.
They selected the correct operation.
They reviewed the change.
They repeated that process hundreds of times.
Then they removed the unsafe option.
In an era that celebrates instant generation, this story is a reminder that the hardest engineering work is often not writing code.
It is understanding exactly what the code must never get wrong.
References
- Linux kernel documentation: Deprecated interfaces, language features, attributes, and conventions
- Kees Cook's public update on the completion of the
strncpy()removal work
This article is an independent technical analysis based on public Linux kernel documentation and maintainer commentary. Kernel development details may continue to evolve as patches are reviewed and released.
Top comments (0)