[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)