[HN Gopher] My first patch to the Linux kernel
___________________________________________________________________
My first patch to the Linux kernel
Author : pooladkhay
Score : 208 points
Date : 2026-03-19 19:51 UTC (3 days ago)
(HTM) web link (pooladkhay.com)
(TXT) w3m dump (pooladkhay.com)
| ashwinnair99 wrote:
| The first one always takes way longer than the code itself
| deserves. Most of the work is figuring out the unwritten rules,
| not writing the patch.
| yu3zhou4 wrote:
| Can confirm that it also happens in other complex systems!
| Still a lot of good time and the novelty factor helps with
| pushing through
| seb1204 wrote:
| Sand that after so many years these rules are still not written
| down.
| fooker wrote:
| This is a big problem in open source that seems taboo to
| discuss.
|
| In my opinion, unwritten rules are for gatekeeping. And if a
| new person follows all the unwritten rules, magically there's
| no one willing to review.
|
| I think this is how large BFDL-style open source projects
| slowly become less and less relevant over the next few decades.
| cromka wrote:
| Agreed. The level of aggressive gatekeepers is just crazy,
| take Linux ARM mailing list for example. I found the Central
| and Eastern Europeans particularly aggressive there and I'm
| saying this as on myself. They sure do like to feel special
| there, with very little soft skills.
| tossandthrow wrote:
| This will likely be alleviated when Ai first projects take
| over as important OSS projects.
|
| Fir these projects everything "tribal" has to be explicitly
| codified.
|
| On a more general note: this is likely going to have a rather
| big impact on software in general - the "engineer to company
| can not afford to loose" is likely loosing their moat
| entirely.
| RustyRussell wrote:
| In the small, it's still a meritocracy. A patch like this is
| obviously correct and I expect to get in first try (maybe
| with a formatting fix by the maintainer).
|
| For large works, the burden shifts, since you are increasing
| the maintenance load. Now we have the question of who will do
| the future work, and that requires judgement of the
| importance of the work and/or the author, and hence is a
| fundamentally political question.
| gus_massa wrote:
| Is this discussed in the article? How hard was to deal with the
| unwritten rules in this case?
| yu3zhou4 wrote:
| Congrats and happy for you, you had a lot of fun and did
| something genuinely interesting
| mbana wrote:
| I love these kind of posts.
| foltik wrote:
| Well done and great writeup! Any idea why the bug hadn't shown up
| sooner, like when running self tests?
| ngburke wrote:
| Sign extension bugs are the worst. Silent for ages then suddenly
| everything is on fire. Spent a lot of time in C doing low-level
| firmware work and ran into the same class of issue more than
| once. Nice writeup, congrats on the patch.
| knorker wrote:
| Integer promotion rules in C are so deceptive.
|
| I don't believe there's anybody who can reason about them at code
| skimming speeds. It's probably the best place to hide underhanded
| code.
| zahlman wrote:
| Wouldn't "underhanded code" there require some ability to
| control UB?
| TuxSH wrote:
| Doesn't need to be UB, you can write expressions like:
| "some_s8_var < some_u32_var" and people will be had. Note
| that is not the same as "some_s8_var < some_u8_var".
|
| -Wextra catches stuff like this, alas I know of a few people
| that think "-Wextra is evil" (even though annoying warnings
| can be selectively disabled)
| NotCamelCase wrote:
| Lovely article with a happy ending!
|
| One thing that I am glad to have been taught early on in my
| career when it comes to debugging, especially anything involving
| HW, is to `make no assumptions'. Bugs can be anywhere and
| everywhere.
| dingensundso wrote:
| Nice blogpost. Was an really interesting read. Would be
| interesting to read about the experience of getting the patch
| accepted and merged.
|
| One thing I noticed: The last footnote is missing.
| siddyboi wrote:
| Huge congrats on tracking that down and getting your first Linux
| kernel patch merged!
| fonheponho wrote:
| Everybody seems to be missing the forest for the trees on this.
|
| There is absolutely no "sign extension" in the C standard (go
| ahead, search it). "Sign extension" is a feature of some assembly
| instructions on some architectures, but C has nothing to do with
| it.
|
| Citing integer promotion from the standard is justified, but it's
| just one part (perhaps even the smaller part) of the picture. The
| crucial bit is not quoted in the article: the specification of
| "Bitwise shift operators". Namely
|
| > The integer promotions are performed on each of the operands.
| The type of the result is that of the promoted left operand.
| [...]
|
| > The result of E1 << E2 is E1 left-shifted E2 bit positions;
| vacated bits are filled with zeros. If E1 has an unsigned type,
| the value of the result is E1x2^E2, reduced modulo one more than
| the maximum value representable in the result type. If E1 has a
| signed type and nonnegative value, and E1x2^E2 is representable
| in the result type, then that is the resulting value; otherwise,
| the behavior is undefined.
|
| What happens here is that "base2" (of type uint8_t, which is
| "unsigned char" in this environment) gets promoted to "int", and
| then left-shifted by 24 bits. You get _undefined behavior_
| because, while "base2" (after promotion) has a signed type
| ("int") and nonnegative value, E1x2^E2 (i.e., base2 x 2^24) is
| _NOT_ representable in the result type ( "int").
|
| What happens during the conversion to "uint64_t" _afterwards_ is
| irrelevant; even the particulars of the sign bit of "int", and
| how you end up with a negative "int" from the shift, are
| irrelevant; you got your UB _right inside_ the invalid left-
| shift. How said UB happens to materialize on this particular C
| implementation may perhaps be explained in terms of sign
| extension of the underlying ISA -- but do that _separately_ ; be
| absolutely clear about what is what.
|
| The article fails to mention the root cause (violating the rules
| for the bitwise left-shift operator) and fails to name the key
| consequence (undefined behavior); instead, it leads with not-a-
| thing ("sign-extension bug in C"). I'm displeased.
|
| BTW this bug (invalid left shift of a signed integer) is common,
| sadly.
| manwe150 wrote:
| It was implementation defined for shifting negative numbers,
| but now the standard specifies twos-complement for this and all
| related IB
| uecker wrote:
| While standard requires twos-complement we did not make all
| shift cases defined so far.
| adrian_b wrote:
| The root problem is actually that the C language allows
| implicit conversions from an unsigned type to a signed type and
| from a signed type to an unsigned type, and in certain contexts
| such implicit conversions are actually mandated by the
| standard, like in the buggy expression from the parent article.
|
| It does not matter which is the relationship between the sizes
| of such types, there will always be values of the operand that
| cannot be represented in the result.
|
| Saying that the behavior is sometimes undefined is not
| acceptable. Any implicit conversion of this kind must be an
| error. Whenever a conversion between signed and unsigned or
| unsigned and signed is desired, it must be explicit.
|
| This may be the worst mistake that has ever been made in the
| design of the C language and it has not been corrected even
| after 50 years.
|
| Making this an error would indeed produce a deluge of error
| messages in many carelessly written legacy programs, but the
| program conversion is trivial and it is extremely likely that
| many of these cases where the compilers do not signal errors
| can cause bugs in certain corner cases, like in the parent
| article.
| uecker wrote:
| You could just use -Wsign-conversion.
| adrian_b wrote:
| Obviously, that should always be used, like also the
| compiler options for checking integer overflow and accesses
| out-of-bounds.
|
| However, this kind of implicit conversions must really be
| forbidden in the standard, because the correct program
| source is different from the one permitted by the standard.
|
| When you activate most compiler options that detect
| undefined behaviors, the correct program source remains the
| same, even if the compiler now implements a better behavior
| for the translated program than the minimal behavior
| specified by the standard.
|
| That happens because most undefined behaviors are detected
| at run time. On the other hand, incorrect implicit
| conversions are a property of the source code, which is
| always detected during compilation, so such programs must
| be rejected.
| uecker wrote:
| The standard will not forbid anything that breaks
| billions of lines of code still be used and maintained.
|
| But it is easy enough to use modern tooling and coding
| styles to deal with signed overflow. Nowadays, silent
| unsigned wrap around causing logic errors is the more
| vexing issue, which indicates the undefined behavior
| actually helps rather than hurts when used with good
| tooling.
| adrian_b wrote:
| Silent unsigned wrap around is caused by another mistake
| of the C language (and of all later languages inspired by
| C), there is only a single unsigned type.
|
| The hardware of modern CPUs actually implements 5
| distinct data types that must be declared as "unsigned"
| in C: non-negative integers, integer residues a.k.a.
| modular integers, bit strings, binary polynomials and
| binary polynomial residues.
|
| A modern programming language should better have these 5
| distinct types, but it must have at least distinct types
| for non-negative integers and for integer residues. There
| are several programming languages that provide at least
| this distinction. The other data types would be more
| difficult to support in a high-level language, as they
| use certain machine instructions that compilers typically
| do not know how to use.
|
| The change in the C standard that was made so that now
| "unsigned" means integer residue, has left the language
| without any means to specify a data type for non-negative
| integers, which is extremely wrong, because there are
| more programs that use "unsigned" for non-negative
| integers than programs that use "unsigned" for integer
| residues.
|
| The hardware of most CPUs implements very well non-
| negative integers so non-negative integer overflow is
| easily detected, but the current standard makes
| impossible to use the hardware.
| uecker wrote:
| There are other languages such as Ada that allow you to
| more precisely specify such things. Before requesting
| many new types for C, one should clarify why those
| languages did not already replace C.
|
| I agree though that using "unsigned" for non-negative
| integers is problematic and that there should be a way to
| specify non-negative integers. I would be fine with an
| attribute.
|
| The problem is also that the standard committee is not
| the ruling body of the C language. It is the place where
| people come together to negotiate some minimal
| requirements. If you want something, you need to first
| convince the compilers vendors to implement it as an
| extension.
| xgk wrote:
| > _CPUs actually implements 5 distinct data types_
|
| Yes, that's true, but the registers themselves are
| untyped, what modern CPUs really implement is multiple
| instruction semantics over the same bit-patterns. In
| short: _same bits, five algebras_! The algebras are given
| by different instructions (on the same bit patterns).
|
| Here is an example, the bit pattern 1011:
|
| * as a non-negative integer: 11. ISA operations: Arm
| UDIV, RISC-V DIVU, x86 DIV
|
| * as an integer residue mod 16: the class [11] in Z/16Z.
| ISA operations: Arm ADD, RISC-V ADD/ADDI, x86 ADD
|
| * as a bit string: bits 3, 1, and 0 are set. ISA
| operations: Arm EOR, RISC-V ANDI/ORI/XORI, x86 AND.
|
| * as a binary polynomial: x^3 + x + 1. ISA operations:
| Arm PMULL, RISC-V clmul/clmulh/clmulr, x86 PCLMULQDQ
|
| * as a binary polynomial residue modulo, say, x^4 + x +
| 1: the residue class of x^3 + x + 1 in GF(2)[x] / (x^4 +
| x + 1). ISA operations: Arm CRC32* / CRC32C*, x86 CRC32,
| RISC-V clmulr
|
| And actually ... the floating point numbers also have the
| same bit patters, and could, in principle reside in the
| same registers. On modern ISAs, floats are usually
| implemented in a distinct register file.
|
| You can use different functions in C on the bit patterns
| we call unsigned.
| throw_await wrote:
| Those billions of lines are already broken by definition.
| uecker wrote:
| Sure, buddy.
| ojeda wrote:
| > which indicates the undefined behavior actually helps
| rather than hurts when used with good tooling
|
| No, one doesn't need undefined behavior for that at all
| (which does hurt).
|
| What actually helps is diagnosing the issue, just like
| one can diagnose the unsigned case just fine (which is
| not UB).
|
| Instead, for this sort of thing, C could have "Erroneous
| Behavior", like Rust has (C++ also added it, recently).
|
| Of course, existing ambiguous C code will remain to be
| tricky. What matters, after all, is having ways to
| express what we are expecting in the source code, so that
| a reader (whether tooling, humans or LLMs) can rely on
| that.
| gus_massa wrote:
| Integer overflow and accesses out-of-bounds must be
| checked at runtime that makes the program slower. It
| looks like -Wsign-conversion can be checked at
| compilation time, perhaps with a few false positives
| where the numbers are "always" small enough.
|
| Does it also complain when the assigned variable is big
| enough to avoid the problem? Does the compiler generate
| slower code with the explicit conversions?
|
| It looks like an nice task to compile major projects with
| -Wsign-conversion and send PR fixing the warnings.
| (Assuming they are only a few, let's say 5. Sending an
| uninvited PR with a thousand changes will make the
| maintainers unhappy.)
| fonheponho wrote:
| > It does not matter which is the relationship between the
| sizes of such types, there will always be values of the
| operand that cannot be represented in the result.
|
| It's not that bad actually; not "always". The only nontrivial
| case is when, as a part of the _usual arithmetic conversions_
| , you (perhaps unwittingly) convert a signed integer type to
| an unsigned integer type [*], and the original value was
| negative.
|
| [*] This can happen in two cases (paraphrasing the standard):
|
| - if the operand that has unsigned integer type has rank
| greater than or equal to the rank of the signed integer type
| of the other operand,
|
| - if the operand that has signed integer type has rank
| greater than or equal to the rank of the unsigned integer
| type of the other operand, but the signed integer type cannot
| represent all values of the unsigned integer type.
|
| Examples: (a) "unsigned int" vs. "signed int"; (b) "long
| signed int" vs. "unsigned int" in a POSIX ILP32 programming
| environment. Under (a), you get conversion to "unsigned int";
| under (b), you get conversion (for both operands) to "long
| unsigned int".
|
| Section "3.2 Conversions | 3.2.1 Arithmetic operands |
| 3.2.1.1 Characters, and integers" in the C89 Rationale
| <https://www.open-
| std.org/Jtc1/sc22/WG14/www/C89Rationale.pdf> is worth
| reading. (An updated version of the same section is included
| in the C99 Rationale <https://www.open-
| std.org/jtc1/sc22/wg14/www/C99RationaleV5.1...> under
| 6.3.1.1.)
|
| It deals precisely with the problem highlighted in the blog
| post. I'll quote just the beginning and the end:
|
| > Since the publication of K&R, a serious divergence has
| occurred among implementations of C in the evolution of
| integral promotion rules. Implementations fall into two major
| camps, which may be characterized as _unsigned preserving_
| and _value preserving_. [...]
|
| > The unsigned preserving rules greatly increase the number
| of situations where unsigned int confronts signed int to
| yield a questionably signed result, whereas the value
| preserving rules minimize such confrontations. Thus, the
| value preserving rules were considered to be safer for the
| novice, or unwary, programmer. After much discussion, the
| Committee decided in favor of value preserving rules, despite
| the fact that the UNIX C compilers had evolved in the
| direction of unsigned preserving.
|
| > QUIET CHANGE -- A program that depends upon unsigned
| preserving arithmetic conversions will behave differently,
| probably without complaint. This is considered the most
| serious semantic change made by the Committee to a widespread
| current practice.
| zahlman wrote:
| > It does not matter which is the relationship between the
| sizes of such types, there will always be values of the
| operand that cannot be represented in the result.
|
| Hmm? Seems to me that unsigned -> larger signed works,
| although other conversions may not.
|
| But yes, I generally agree that these are terrible
| conversions to do implicitly, given that the entire point of
| those types is to control the interpretation of memory at a
| bits-and-bytes level. Languages where implicit numeric
| conversions make sense are generally not languages that care
| so much about integer size, and the entire point of having
| unsigned types is to bake that range constraint in.
| Arch-TK wrote:
| It's incredibly common for people talking about C online or
| even in books (be that blog posts, side notes, tutorials,
| guides) to constantly make mistakes like these.
|
| C seems to be one of those languages where people think they
| know it based on prior and adjacent experience. But it is not a
| language which can be learned based on experience alone. The
| language is full of cases where things will go badly wrong in a
| way which is neither obvious nor immediately evident. The
| negative side effects of what you did often only become evident
| long after you "learn" it as something you "can" do.
|
| If you want to write C for anything where any security, safety,
| or reliability requirement needs to be met, you should commit
| to this strategy: Do not write any code which you are not
| absolutely certain you could justify the behaviour of by
| referencing the standard or (in the case of reliance on a
| specific definition of implementation defined, unspecified, or
| even (e.g. -ftrapv) undefined behaviour) the implementation
| documentation.
|
| If you cannot commit to such a (rightfully mentally arduous)
| policy, you have no business writing C.
|
| The same can actually be applied to C++ and Bash.
| uecker wrote:
| It is definitely not a language that can be learned by
| reading blogs.
|
| But the advice really applies to almost everything you do
| related to security, safety and reliability. In other
| languages you may have a panic in production or a supply
| chain issue.
| zahlman wrote:
| > Do not write any code which you are not absolutely certain
| you could justify the behaviour of
|
| Doing this for every line is impossibly tedious (people will
| quickly tire of it), and detecting where the code is actually
| non-trivial requires a kind of epistemic humility that
| doesn't come naturally to most.
|
| Better if we can use languages that don't assume such demands
| are necessary for the compiler to be able to generate
| performant code.
| msichert wrote:
| That's very interesting, I'm only familiar with the C++
| standard where bit shifts are defined in terms of
| multiplications and divisions by powers of 2:
| https://eel.is/c++draft/expr.shift
|
| So it seems in regard to bit shifts, C++ behaves slightly
| differently (it seems to have less UB) than C.
| qingcharles wrote:
| Fortunately, the solution should be valid for all
| circumstances, though the working out might have gone a bit
| astray.
| ozgrakkurt wrote:
| Great blog post. Using _BitInt typedefs for integers is a good
| option for anyone starting a fresh c project. It has worked well
| for me so far. _BitInt integers don't promote to signed
| automatically like regular integers in c
| uecker wrote:
| That makes me worry that you code actually has more issues
| because with small _BitInt you would run into signed overflow
| more often.
| ozgrakkurt wrote:
| I use "-fno-strict-overflow" so it shouldn't be ub in any
| case. Also basically never use signed integers and I always
| use "checked" methods for doing aritmetic except performance
| critical loops.
| monus wrote:
| > You may wonder whether I tried asking an LLM for help or not.
| Well, I did. In fact it was very helpful in some tasks like
| summarizing kernel logs [^13] and extracting the gist of them.
| But when it came to debugging based on all the clues that were
| available, it concluded that my code didn't have any bugs, and
| that the CPU hardware was faulty.
|
| This matches my experience whenever I do an unconventional or
| deep work like the article mentions. The engineers comfortable
| with this type of work will multiply their worth.
| TheOpenSourcer wrote:
| Welcome to the club my friend! Its very exited. Very soon you
| will choose your favorate subsystem and double down on it.
| TacticalCoder wrote:
| A big thanks for making the Linux kernel better!
|
| > Since virtualization is hardware assisted these days
|
| I was running Xen with full-hardware virtualization on consumer
| hardware in... 2006. I mean: some of us here were running
| hardware virt before some of the commenters were born. Just to
| put the "these days" into perspective in case some would be
| thinking it's a new thing.
| kwar13 wrote:
| Huge congrats. One of the highest achievements as a programmer in
| my book!
| himata4113 wrote:
| I am just an awe after reading: "The motherboard would be stuck
| in a zombie state" because that's EXACTLY what happened to me
| with normal KVM and QEMU (with modifications)! I kinda just
| pulled the plug and continued working never to have this
| resurface again... until I continued reading... I thought I was
| doing something wrong in the user-land, turns out it was UB sign
| shift all along.
___________________________________________________________________
(page generated 2026-03-22 23:01 UTC)