Regression in Linux 4.14 – using bcache can destroy the file system
bugs.gentoo.org
bugs.gentoo.org
I never said that and, well, no shit they don't "patch themselves", so right off the bat your premise is wrong.
> you're already using grsecurity/PaX patches
Uh, no. The grsec developer is GPL violator.
It's always a question of "pick your poison": Debian stable users actually missed out on heartbleed completely because Lenny's OpenSSL was too old to have it.
I really think gentoo has the best packaging story of Linux distros, except perhaps 'newer' stuff like nixos/guix. Rolling releases (my gentoo box has a continuity since 2009 with nothing ever resembling a dist-upgrade of other distros that you're forced to do to get the latest and greatest), generally gets the latest versions of stuff and their patches first or among the first if you want to live on the bleeding edge, it's easy to find overlay packages for things not in the main tree, and even create your own 'packages' since everything is source-based, USE flags for customization, and anyway it has a good stable-only option too.
It's definitely possible to go overboard and get into trouble. But there's a lot to help. And with gentoo I was able to weather the gnome3/systemd madness and keep that stuff off my system despite having to keep around old gnome2 and udev packages until mate, eudev, etc. were ready. I don't know too much about Arch except the pain stories friends have relayed as they eventually gave up and switched to something else. Back when Arch decided to make Python 3 the default python well before it was ready someone on IRC quipped "<dash> well that confirms my impression that arch was invented by a bunch of guys who thought gentoo was too stable and easy to use".
No, because bcache only works for a block device, whereas NFS presents itself as a pseudo filesystem (NFS) to the target.
You could however use bcache as a local accelerator for a remote iSCSI target.
bio->bi_partno = bio_src->bi_partno;
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/lin...
Quoting myself from https://www.reddit.com/r/programming/comments/61rh9j/curl_is... :
> For instance, the patch for CVE-2016-5420, "Incorrect reuse of client certificates", is about making sure that when you compare two structs, you don't forget to compare a field in the two structs. In C, you have to open-code this comparison. In Rust, you just stick #[deriving(Eq)] before the struct, because unlike C, Rust knows that comparison of structs is a thing that people might want to do ever. The compiler will generate a trait Eq implementation that compares all the fields, using the Eq implementations (either automatically-derived or manual) of each field, so you even get secure string compares if you define some SecureBuffer type and use it for the strings. (As a bonus, doing that also makes sure that you don't accidentally use a normal strcmp on the strings.) When you add a new field to the struct, the automatic Eq implementation on the struct will automatically compare it, too.
(Disclaimer: while I happen to like Rust, there are plenty of other languages you could also use instead—Microsoft Research even made a fork of C# designed for kernelspace. And while I am interested in writing Rust, I'm not particularly interested in telling people who are happy writing C that they should stop, just in making sure that people considering a language for a new project know what each language brings to the table.)
https://github.com/curl/curl/blob/8aee8a6a2d/lib/vtls/vtls.c#L94
and not this: https://github.com/curl/curl/blob/master/lib/vtls/vtls.c#L94
(Deliberately disabling truncation on the URL, at the cost of losing clickability. This comment isn't really about these specific links anyway, just the 'syntax' of the URL.) struct Example {
x: i32,
y: i32,
}
fn example_clone(input: &Example) -> Example {
Example {
x: input.x,
// Oops, forgot y
}
}
This fails with a following error: error[E0063]: missing field `y` in initializer of `Example`
--> src/main.rs:7:5
|
7 | Example {
| ^^^^^^^ missing `y`Also, even if somebody did insist on explicit mut-output pattern in Rust anyway, they would quickly realize that they need to initialize the value before getting a mutable reference to it - Rust NEVER allows access to uninitialized memory (other than unsafe blocks where you can trick Rust into accesing uninitialized memory and cause undefined behavior by using `std::mem::uninitialized`). The noise related to it should hint that this is not the intended way to do it. Here is a code example to clarify what I mean.
fn write2(out: &mut i32) {
*out = 2;
}
fn main() {
let mut two;
write2(&mut two);
}
And error message: error[E0381]: use of possibly uninitialized variable: `two`
--> src/main.rs:7:17
|
7 | write2(&mut two);
| ^^^ use of possibly uninitialized `two`
Fixing that requires explicitly writing a value into `two`, for instance with `let mut two = 0`, which for complex structs... yeah, you may as well return a struct directly instead of bothering with pointer output.[std::mem::uninitialized]: https://doc.rust-lang.org/std/mem/fn.uninitialized.html
It just memsets the entire thing to 0, which in Rust would
1. require unsafe{}
2. most likely be UB as the struct looks like it has a bunch of pointers which would not be nullable (e.g. arrays)
And therefore not be done at all, or rejected by reviewers if attempted.
That would've caused this code change to fail, unless I'm missing something.
The entire structure was just memset to 0, it's not like the struct was carefully initialised.
C++ or Rust might have put a 0 in the uninitialized member, but the bug would have remained.
If you don't override it, it will copy everything properly.
> Rust might have put a 0 in the uninitialized member
Rust does not do that. If a member is not initialised, it's uninitialised and in this case the structure will be rejected. Here even if you could not just have derived Clone (struct bio looks non-trivial) your hand-rolled implementation would have complained that you had not properly initialised the member.
Sure, so does memcpy or bare struct assignment in C. The problem is that trivial copy constructors are relatively rare.
> Here even if you could not just have derived Clone (struct bio looks non-trivial) your hand-rolled implementation would have complained that you had not properly initialised the member.
Right, but this is more similar to an assignment operator. DerefMut in Rust I think wouldn't have caught the bug.
Only if you don't zoom out. If you do, this is alloc/init/fill which in C++ or Rust would use a regular copy and RVO.
Typical Linus rant. There's no substantive, logical, reasoned argument in there about why C++ would be inappropriate. The closest we get is that "You invariably start using the "nice" library features of the language like STL" — God forbid the language provide a dynamically sized array, and not force you to implement it manually.
Not saying that Linux should switch from C to C++, nor that doing so wouldn't be a metric crap ton of work (it would be) and for benefits that might be hard to sell (e.g., at this point, we already have a decent vector implemented in C macros; what does switching actually gain us?) or that the kernel doesn't have special concerns (in a kernel, you have to implement malloc(), for example (and that's probably a gross simplification) and that could definitely affect a lot of things in the STL), etc. Just that the linked rant is not valid argument as to why C++ isn't a good fit.
Thing is, it's a very real issue. C++ as written by the best of programmers might be a very reasonable language for the kernel, but in practice what happens is the additional abstraction and indirection mechanisms mean it becomes very hard to decipher with any confidence code written by mediocre or merely average programmers.
And this is a real issue in practice, I saw it when I was at Google with code written by other programmers at Google, and honestly the majority of the code in the Linux kernel is at about the same level - some of it really good elegant code, more of it ugly and hacky but working, and a lot more (especially driver code) mediocre crap.
And when having to decipher and refactor the mediocre crap - because let's be honest, that's the majority of the code - I'd much rather have to deal with C than C++.
The reason this is such an issue with C++ is that - and it's been said before, but it bears repeating - C++ adds a lot of new ways to shoot yourself in the foot and it doesn't take any of the old ones away. With C, you can generally read a chunk of code and have confidence that it does what it appears to do - you can understand it in isolation. With C++, just figuring out the flow control can be a nightmare.
Rust would be a different story. I am 100% in favor of using Rust whenever possible - it has some of the same issues as C++ or any other polymorphic language in that figuring out flow control can really suck, but it provides much stronger guarantees w.r.t. how code can screw you over that make it well worth it. C++... not so much.
Linus's argument against C++ is basically akin to Dijkstra's argument against `goto`. Like any tool, it can be abused. But they've seen too many abuses for their own taste, so they ban the tool completely despite the advantages the tool may have.
Monotone uses boost, standard C++ practices, and it's much slower and more bloated than git.
The idea is that, even if git could be written in C++, with similar performance compared to the C version, C++ programmers would invariably end with something closer to monotone than git.
C programmers: you're not off the hook - malloc/free are just as bad, which is why the kernel has it's own context appropriate memory allocaters.
And of course don't forget that malloc/free (and I assume new/delete) are not safe to be used inside user space signal handlers
C++ has changed quite a bit in the last 10 years.
1: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/lin...
2: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/lin...
3: https://git.kernel.org/pub/scm/fs/xfs/xfstests-dev.git/
http://www.h-online.com/open/news/item/Bug-in-Linux-kernel-c...
But if anyone is running critical data, they shouldn't be using a bleeding-edge kernel in the first damn place. It certainly "sucks" for the users that found it, but again, that's the risk you play.
And, this bug seems to have been found... a mere week after articles even reported the release of Kernel 4.14:
https://bugs.gentoo.org/638206
http://www.omgubuntu.co.uk/2017/11/linux-kernel-4-14-lts-fea...
Which is a pretty amazing turn-around time for "crowd-sourced" discovery of a bug. And according to the bug tracker, it looks like they patched it the same day.
So as bad as this is (and I feel for those who lost data, I really do), the solution is simply to not run bleeding-edge kernels on release day. The very fact they have a minor version number 4.14.1 (.1) implies things get released that need fixed, and if you have critical data you should be more patient.
> risk you play
uhm you realize that the kernel was already released upstream right?
Using the newest upstream kernel is something you opt into, on non-production hardware. This isn't something that would impact a regular user.
The nice thing about asking on hacker news is you get answers from experts, and you can often get insider knowledge that really isn't available anywhere on google.
But thanks for the link, it's a good one ;)
The nice thing about doing a minute of preliminary research when you have a question is that the questions you ask afterwards will be more interesting.
But after about the third time that upgrading my system made it unbootable because I had missed some crucial step buried in the changelog or release notes, I also developed a deep appreciation of all the things modern distros handle for you. I rarely fear a dist-upgrade the way I did an emerge world.
Arch mostly just works. The big issue I run into is that the upgrades are _almost_ flawless, which bites you once a year or so when you least expect it.
However now I think about Gentoo as a more-adult one. It's really a meta-distribution. It was and is misunderstood by a lot of people. Teenage me included. Gentoo is deceivingly easy to setup - documentation is excellent and tooling mature.
I think that the best way to use Gentoo is to really set all the USE flags and all the unmasking to one's will, but not for a single computer. There should be a binhost - a binary package server available for whole fleet of computers. You can also cross compile for a totally different architecture. Then the customization comes in handy.
Also now it's certainly easier than when I've done it. I used Gentoo on a desktop with a whooping 512MB RAM. Now compile times are significantly lower.
To sum it up: use Gentoo to build a custom Linux distribution for fleet of computers or for embedded devices. That's it's true power.
Google chose Portage for ChromeOS building for a reason.
My media PC has Void Linux on it, which is also very promising. I'd also suggest looking at Funtoo; which I used briefly on a laptop and it came with a lot of good sane defaults.
"Experience is what you get when you didn't get what you want."
Best to only upgrade Linux when the next release is already out (e.g. 4.15 out to consider 4.14). Or just use something else.
Can you spot the error?
Here is the commit that fixes the bug: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/lin...
Is it just a merge commit? I imagine it's probably unfair to blame Linus for this (assuming it's a huge merge), but it would also weird for you to point to a merge commit as evidence of the opposite.
Color me confused.
As a software developer, the habit committing smaller changes has saved me a lot of time. Even more so, a clean distinction between refactorings (e.g. moving or renaming things) and functional changes (e.g. adding or fixing things) is vital to any large code base.
As the saying goes: "for each desired change, make the change easy (warning: this may be hard), then make the easy change" (Kent Beck)
Jokes aside, what you see it usually just the squashed commit submitted for merge. With 6 million git objects, you reconsider twice having each change in a single commit.
With my Linux maintainer hat on, I would have never accepted that commit.
Then __david__'s argument "could you spot the error?" is moot, because the squashed commit (hopefully!) was not the subject of review, but the smaller commits.
Are you the creator of git? If not, how exactly are you qualified to judge "proper" usage -- by YOUR standard you're not qualified yourself, so...?
(I realized it was a joke, but I offer the above as a counterpoint to the satirical/ironic element of the joke. My opinion is that the originator doesn't get special treatment. But then the point wasn't git, it was what/how things get merged into the Linux kernel. The mechanism is entirely immaterial.)
Squashing commits for merge into Linux is not standard practice AFAIK... and happily I see an Linux committer has async'ly backed me up on this.
Generally the preference I see, where git is concerned (not sure about the kernel), is that everything is rebased right into the master branch to avoid merge graphs getting absurd.
Still, i think squashing still happens in the public trees:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/lin...
EDIT: Actually, thats indeed a merge, but the diff is still showed with it, which confused me (because conflict-less merges are usually +0/-0).
The structure dm_bio_details was changed to remove one field that points to a block_device structure, and add a field that points to a gendisk structure, and an integer called bi_partno.
This change copies the gendisk structure, but not bi_partno. That's the bug.
From this patchset, there's now at least four different fixes: tags referring to it in Linus's tree. The patchset itself was broken and intermediate states didn't compile, making bisection annoying. And there's many other callers of this function that are potentially getting bad results, along with other cousins like bio_clone_bioset that are still unfixed.
This was not a fun experience for a new maintainer. It turns out if you had created a fresh bcache on 4.14, like most of my tests do-- you're probably fine. And not all environments will experience problems. It went in a couple of weeks before I became maintainer, and isn't in the bcache code itself (and wasn't reviewed by anyone who worked on bcache, and was never sent to the linux-bcache mailing list.) I've learned some new things to add to my test suite, and I guess i need to read all the block diff to make sure people don't break things out from under me.
As a maintainer, isn't it then your right to refuse the patchset and demanding a clean patchset instead?
For stuff I accept, I try and make sure that intermediate states compile, but I can't promise I run a compile on every intermediate step or that my review eye is good enough to spot every wrongly-ordered-dependent-change. It would be nice if someone would throw hardware at building intermediate states of linux-next and sent nastygrams to maintainers/committers about it: might make people more careful.
I have a continuous integration tool for bcache in place, but it's more geared on end states-- compiling/checkpatch/test sequence.
linux git:(c2ee070fb003) cat patch.coci
@@
expression lhs;
expression rhs;
@@
- lhs->bi_bdev = rhs;
+ bio_set_dev(bio, rhs);
so it spotted the original missing bi_partno = in the same file. diff -u -p a/block/bio.c b/block/bio.c
--- a/block/bio.c
+++ b/block/bio.c
@@ -596,7 +596,7 @@ void __bio_clone_fast(struct bio *bio, s
* most users will be overriding ->bi_bdev with a new target,
* so we don't set nor calculate new physical/hw segment counts here
*/
- bio->bi_bdev = bio_src->bi_bdev;
+ bio_set_dev(bio, bio_src->bi_bdev);
bio_set_flag(bio, BIO_CLONED);
bio->bi_opf = bio_src->bi_opf;
bio->bi_write_hint = bio_src->bi_write_hint;
@@ -681,7 +681,7 @@ struct bio *bio_clone_bioset(struct bio
bio = bio_alloc_bioset(gfp_mask, bio_segments(bio_src), bs);
if (!bio)
return NULL;
- bio->bi_bdev = bio_src->bi_bdev;
+ bio_set_dev(bio, bio_src->bi_bdev);
bio->bi_opf = bio_src->bi_opf;
bio->bi_write_hint = bio_src->bi_write_hint;
bio->bi_iter.bi_sector = bio_src->bi_iter.bi_sector;
but it also spotted a missing assignment in the same file in the bio_clone_bioset function. i don't know enough about this functionality to know if the assignment is meant to be here as well. also, considering it is in the same file i would have thought if it was necessary it would have been noticed in the last patch. just thought i would let someone else who would have a better idea about how this works know.i also haven't checked the rest of the coccinelle output. i know it has generated a lot of _wrong_ code. like the actual patch looks like it has done something more intelligent than what coccinelle blindly did.
To quote what you replied to: "And there's many other callers of this function that are potentially getting bad results, along with other cousins like bio_clone_bioset that are still unfixed."
bcache doesn't call bio_clone_bioset. I put in the minimum change to fix the bcache regression, for quick review and acceptance. You're free to submit this to linux-block if you want.
GitHub encourages you to make a bunch of smaller commits and then roll them all up into one pull request -- so the unit of change is the PR, and not so much the single commit.
In kernel-dev land, the unit of change is the commit. Sometimes larger changes will be broken up into a series of smaller commits, but in general you tend to see single-commit changes (which often come from a series of commits someone has done and then squashed down to one).
Patches that are submitted by external contributors are never squashed by maintainers (except to occasionally fix a bug that was discovered after the submission).
As this bug points out, being a maintainer doesn't suddenly make all your code bug free..
EDIT: OTOH, most of the Linus released kernels go through months of bug fixing before they show up in any of the stable distributions. Its the "rolling release" distro's which get hit by these bugs because they have this strange idea that once Linus (or whatever package) releases something its done cooking.
Besides, this wasn't a security bug, was it? It corrupted the filesystem, it didn't give anyone else any undesired access did it?