underlap

I am Glyn Normington, a retired software developer, interested in helping others improve their programming skills.

The so-called “Dirty Pipe” CVE-2022-0847, detailed in Max Kellerman's article, was published on 7 March 2022. I recently upgraded my kernel to the stable version 5.16.11. So is there a new stable version with a fix?

According to a post on the kernel mailing list, the fix is in 9d2231c5d74e (lib/iov_iter: initialize "flags" in new pipe_buffer).

However, the change logs for 5.16.12 and 5.16.13 do not mention 9d2231c5d74e or lib/iov_iter. So has the fix still not made it into a stable kernel?

A UNIX stack exchange answer to question “Given a git commit hash, how to find out which kernel release contains it?” helped here. The github page for commit 9d2231c5d74e shows that, at the time of writing, the fix is part of v5.17-rc7 and v5.17-rc6. So it seems like the fix isn't yet available in a stable kernel.

Postscript: 21 March 2020

According to the releases page at kernel.org:

After each mainline kernel is released, it is considered “stable.”

v5.17, containing the fix, was released yesterday, so I upgraded to that.

This morning I followed these instructions to upgrade Ubuntu to a development release of 22.04 and the kernel to 5.16.11. I was hoping that a bug would be fixed, but it turn out not.

I was previously on 21.10:

$ lsb_release -a
LSB Version:	core-11.1.0ubuntu3-noarch:printing-11.1.0ubuntu3-noarch:security-11.1.0ubuntu3-noarch
Distributor ID:	Ubuntu
Description:	Ubuntu 21.10
Release:	21.10
Codename:	impish

with kernel 5.15.0-rc7:

$ uname -r
5.15.0-051500rc7-generic

I upgraded to a development branch of Ubuntu 22.04:

$ lsb_release -a
LSB Version:	core-11.1.0ubuntu3-noarch:printing-11.1.0ubuntu3-noarch:security-11.1.0ubuntu3-noarch
Distributor ID:	Ubuntu
Description:	Ubuntu Jammy Jellyfish (development branch)
Release:	22.04
Codename:	jammy

and to a stable kernel version 5.16.11:

$ uname -r
5.16.11-051611-generic

If I find any issues, I'll mention them here.

Notes

The following are some notes of errors and interesting observations during the upgrade process.

Third party repository error

During the upgrade process, I notice the following error:

E: The repository 'http://ppa.launchpad.net/gezakovacs/ppa/ubuntu impish Release' does not have a Release file.
N: Updating from such a repository can't be done securely, and is therefore disabled by default.
N: See apt-secure(8) manpage for repository creation and user configuration details.

This was caused because I installed UNetbootin from the above third party repository, which doesn't support all releases.

Failure upgrading installed packages

When running sudo apt-get upgrade, the system rebooted and I had to re-run the command and follow some recovery instructions to run:

$ sudo dpkg --configure -a
$ sudo apt-get upgrade
$ sudo apt --fix-broken install

Re-running sudo apt-get upgrade then succeeded.

Upgrading the distribution

Towards the end of sudo apt-get dist-upgrade, I notice the following output:

update-initramfs: Generating /boot/initrd.img-5.15.0-051500rc7-generic
update-initramfs: Generating /boot/initrd.img-5.13.0-30-generic
update-initramfs: Generating /boot/initrd.img-5.13.0-28-generic

So it seems the distribution upgrade is preserving my current kernel version as well as a couple of previously installed kernel versions.

Later, the following output:

* dkms: running auto installation service for kernel 5.15.0-18-generic

showed the upgrade was installing a later 5.15 kernel.

Here's how I got involved in the IETF JSONPath Working Group.

A couple of years ago I found myself needing to use JSONPath in a work project, but I couldn't find a spec other than Stefan Gössner's original article. Since the project was in Go, I looked for a Go implementation of JSONPath which documented the syntax and semantics clearly. Since the project was aimed at Kubernetes, I also needed it to accommodate YAML. I ended up specifying and implementing a new Go implementation: yaml-jsonpath.

Towards the end of this effort, I decided there was a need for a standard¹. I settled on the IETF and gathered together a small group of JSONPath implementers (found via Christoph Burgmer's excellent JSONath comparison project). Together we cooked up an initial spec and published it as an internet draft.

Around the same time, Stefan Gössner and IETF veteran Carsten Bormann submitted their own draft based closely on Stefan's original article. We joined forces in a Working Group with the help of James Gruessing, Tim Bray, and others. After agreeing a charter, we merged our drafts, and have been iterating on the spec for about a year.

There are a few loose ends, but we hope to begin the submission process for an RFC before very long. This may be the end of the beginning, rather than the beginning of the end.

Footnote

¹ I'd worked in software standards in the past, such as OSGi and JSR 291, which brought OSGi into the Java Community Process.

You craft a code change with tests, comments, and maybe even some documentation. But when you commit the change, it's tempting to rush. Instead, spare a thought for the humble commit message.

A well-written commit message can be like gold dust later — possibly much later — in the project. It's a chance to record why you made the change, any compromises you made, stuff that still needs to be done, and perhaps alternative approaches you rejected.

Commit messages, sometimes called commit logs, are like documentation which never goes out of date because they relate to a specific commit. They don't clutter up your code. They are there for the asking when you need them,

If you've ever been faced with working out what a piece of code is doing and why it is the way it is, good commit messages can give a unique insight into the coder's mind.

The following links give some ideas and examples for writing better commit messages: * How to Write a Git Commit Message * Conventional Commits * My longest ever commit message

Testing your code is a great way of avoiding bugs, but it's tempting to put off writing tests until you have something which works. At that point, it may be very difficult to test your code, especially to write unit tests, because you may not have written the code with testing in mind. One way to avoid this situation is to use Test Driven Development (TDD).

What is TDD?

TDD is a way of developing code by writing tests before the code to be tested. This way, you can be sure that your code can be tested. Also, you'll know that your tests are valid because they'll start off failing (a test which never fails is useless!).

How do I start doing TDD?

The basic process is easy: 1. Write a failing test 2. Make the test pass, together with any other tests 3. Refactor to make the code clean (described below) 4. Repeat as necessary.

A failing test might not even compile if it's calling some code which hasn't been written yet. That's ok.

This process is sometimes called “red, green, refactor” after the colours that some tools use to flag failing and passing tests.

Unit testing

A unit test tests some code in isolation (from other code, the internet, a database, etc.). Code composed of small modules is easier to test, if each module can be tested in isolation. It's then possible to drive unusual and error paths as well as the happy paths through the code.

The trick is deciding the size of “unit” to test, e.g. it could be a single module or a group of modules. If the unit size is larger, there's more scope for refactoring without needing to change the tests. If it's smaller, then moving code between modules and changing interfaces tends to require test rework.

Refactoring

The term refactoring is used in two different ways. The first kind of refactoring is to start with a piece of code and all its tests passing and then to make essentially arbitrary changes to the code, often in small steps, ensuring that the tests continue to pass.

So, starting with all the tests passing, the process is: 1. Change the code 2. Run the tests again 3. If some fail, fix the code or undo the change until all the tests pass 4. Repeat as necessary.

The second kind of refactoring is to make specific changes to the code which are known to preserve the behaviour. Sometimes this can be assisted by IDEs or editors with automatic “refactorings”, such as “extract method”, “rename variable”, and so forth. After doing this kind of refactoring, it's still worth checking all the tests still pass.

Modifying tests

Strictly speaking, if you modify tests, you should check they still catch the failures they were originally written to catch, but that's a real pain as you would have to temporarily break the code under test to provoke each modified test to fail. But without this, it's theoretically possible to mess up a test change and end up with a test which passes when it shouldn't. An extreme example of this would be to delete the code inside a test, which would obviously then pass, but be useless. The issue is that people might be tempted to hack their test code around on the assumption that running all the tests and seeing them pass is some sort of safety net when, in fact, it isn't.

The best way to avoid modifying tests excessively is by testing larger units. If you do need to modify tests, then doing a series of correctness-preserving refactorings reduces the risk of invalidating a test. But the rule of thumb is to be extra careful when modifying tests.

Do I need to follow TDD strictly?

It's quite a good discipline to follow the strict TDD approach for a while to get the hang of it. But after that, I think it's fine to be a bit more relaxed.

For instance, if you're trying to get a piece of code working, it may be more appropriate to code up a prototype as a “proof of concept” and then go back and develop some code using TDD now that you know roughly how the code will work. This approach is sometimes called “fire, aim, ready” (reversing the well-known phrase “ready, aim, fire”) meaning get something working first, then understand the problem better, then start development proper.

I have also used code coverage tools to make sure most, if not all, of my code is tested. You don't need to hit 100% coverage, but something like 80-90% is clearly a better sign of a good test suite than 20-30%.

Does TDD guarantee my code will be well designed?

No! Don't be caught by the trap of thinking TDD will necessarily give you a great design. It's a good way of avoiding untestable code, but it doesn't guarantee clean, understandable interfaces etc. Using TDD to get a good design is, as Rich Hickey once described, like driving a car along a road and bashing against the crash barriers (“guard rails” if you're American) as a way of getting to where you want to go.

Is TDD worth it on small, personal projects?

I've worked in teams, one doing pretty strict TDD, but on personal projects, I tend to unit test the stuff which I'm most likely to get wrong and with the most conditional logic. (I must confess I don't tend to invest in integration tests for personal projects as it's usually too much bother.)

More information

The classic route is to take a computer science, software engineering, web development, or similar university degree. But perhaps you are already working and don't want to go back to university. Or maybe you've done such a degree, but you're still finding it hard to land your first software development job. Read on.

The key skills you need to demonstrate in order to land a software development job are:

  • fast learning,
  • problem solving,
  • coding, and
  • ability to work in a team.

Learning and coding

It's worth building up a portfolio of coding projects (sometimes known as “side-projects”) in the area you want to apply for. (If you don't know what areas there are, take a look at the resources at the end of this article.) So, if you want to do web programming, you'd build your own web app. Similarly for mobile app development. If you choose some technologies you haven't used before, this will give you opportunity for some fast learning.

Store your portfolio somewhere it's easy to access such as github, bitbucket, codeberg, or similar.

Talking of which, a valuable piece of learning would be to work through a book or tutorial on the git distributed version control system. Make sure you understand how to commit changes, create and merge branches, and push to and pull from a remote git repository.

A general approach to fast learning is to ask yourself questions:

  • What's interesting here?
  • What don't I understand?
  • How could I explain this to someone else?

Read about Richard Feyman's technique for learning and teaching if you want some more ideas.

Problem solving

To practise problem solving, doing gradually harder and harder coding challenges, such as codewars, would be one way, although that's a bit artificial compared to solving problems in your own side-projects.

In particular, you need to experience getting stuck and then finding ways to solve the problem and get unstuck, because that's what software developers spend a lot of their time doing.

You might even want to read a general book on problem solving, such as Solve It!: The Mindset and Tools of Smart Problem Solvers.

One general approach to problem-solving us to narrow down the cause of a problem. Sometimes the symptoms of the problem give a clue as to where the cause of the problem is. Sometimes searching the internet for the symptoms and/or the piece of software causing the problem can turn up others who have experienced similar problems. From them, you can often pick up debugging techniques or sometimes even solutions.

On the other hand, if the problem is you can't figure out how to do something, then another general approach is to generate lots of potential solutions by using idea generation techniques such as brainstorming, forced relationships, and so on (see the resources below for more), and then to try the most promising solutions until you find a way forward.

One last problem-solving tip: take a break! Get some exercise, do something else, or simply take a coffee break. You'll be surprised how often “the penny drops” when you are doing something else.

(If you have done any mathematics in the past, it would be worth brushing up on predicate logic and set theory as these give some nice ways of thinking about programs and data. Also, doing exercises in these subjects is a good way of developing problem solving skills. If you don't have any mathematics, don't worry – many programmers get by with minimal knowledge of mathematics and pick up what they need on the job.)

Team work

Getting experience of working in a team can be tricky. You may be able to find a suitable open source project which you can contribute to. But any teamwork in another setting, such as sports or social activities, is a good start. Programming is far from a solo activity and learning to work well with others is really important.

Conclusion

Finally, to land a software job, you'll need to demonstrate a genuine desire to work in programming by devoting quite a bit of time to developing your skills – I guess maybe 10 hours a week or so. By honing your coding, learning, and problem-solving skills in your spare time, you'll also get a better feel for whether this is the kind of thing you want to do as a job.

Resources

#software #softwaredevelopment #softwareskills