Leading items
Welcome to the LWN.net Weekly Edition for December 14, 2023
This edition contains the following feature content:
- Logo and trademark issues for openSUSE: a contest for a new openSUSE logo creates conflict within the project.
- Controlling shadow-stack allocation in clone3(): the details for how user space should control shadow-stack usage are still being worked out.
- Modern C for Fedora (and the world): an effort to rid a distribution of archaic C code heads toward its conclusion.
- Some recent and notable changes to Rust: an update on significant work within the Rust community.
- Project Bluefin: A customized Fedora Silverblue desktop image: a different approach to an immutable desktop distribution.
This week's edition also includes these inner pages:
- Brief items: Brief news items from throughout the community.
- Announcements: Newsletters, conferences, security updates, patches, and more.
Please enjoy this week's edition, and, as always, thank you for supporting LWN.net.
Logo and trademark issues for openSUSE
A contest for new logos for the openSUSE project and for four separate distributions of it, Tumbleweed, Leap, Slowroll, and Kalpa, has turned into a bit of an uproar in that community. A vote has been held on the candidates and winners have been announced, but some are questioning why there is a need to change the existing logo (the "Geeko" chameleon) at all. In addition, there are questions about whether the new logo will be trademarked (as previous ones have been)—and how many years that will take.
The logo-design contest was meant to try to foster a single unified "look"
that meshes well with the existing logos for the MicroOS, Leap Micro, and Aeon distributions.
Tumbleweed and Leap have existing logos that share the look, but there have
been some "visibility and recognition issues
" with Tumbleweed's;
Leap was added into the mix in case the community thought it should change.
Unifying the look will "strengthen the visual identity of the openSUSE
brand and make
it discernible and cohesive
". The guideline in the contest
announcement said that logos should be "designed with
simple shapes and lines for uniqueness and interest, typically as empty
outlines, although the possibility of using fill is not excluded
". A
look through the submissions will show multiple examples of what is meant.
![openSUSE logo [openSUSE logo]](https://static.lwn.net/images/2023/opensuse-logo.png)
The link to the announcement about voting on the designs was posted to the openSUSE project mailing list on November 23 by Douglas DeMaio, who is a member of the openSUSE Board and is managing the contest. He was encouraging people to read about the contest and to vote for their favorites. But there was no option to vote for "no change" on the overall openSUSE project logo (seen at left), even though the existing Tumbleweed and Leap logos were options; Slowroll and Kalpa do not yet have logos.
Since they could not vote for it, several people noted their happiness with
the existing openSUSE logo in the thread. For example,
"victorhck" said:
"I really don't want to change the main Geecko image for openSUSE.
"
Javier Llorente agreed, noting
that there are "some nice logo designs
", but lamenting the lack of a
"'keep the
current the logo' option
".
"Marcel" raised
the possibility of "legal problems
" with the existing openSUSE logo,
saying that it is too similar to the SUSE logo. Stefan Seyfried expressed
surprise about that; if that was the reason behind the switch, he said,
it should have
been mentioned in the announcement. Former board member (and current SUSE
Distributions Architect) Richard Brown said
he thought that there was not any actual legal problem; "The current
logo is a formally
registered trademark
".
Brown also noted that changing the trademarked openSUSE logo would require
a new trademark registration, which comes with a substantial financial and
time cost, unless the
project is "just abandoning the
idea of openSUSE having a defensible
trademark
". There are multiple problems with not having a trademark,
however, including the trademark
guidelines becoming unenforceable for the logo. In addition, SUSE
would have an interest in ensuring that openSUSE (which, obviously, shares
part of its name with its parent) has a valid trademark.
One of the openSUSE branding contributors, Jacob Michalskie (also known as
"LCP"), pointed
out that while some of their designs were part of the contest, there
was some confusion in the messaging about the logos. They were concerned
that the
vote will not be "representative of the
intent of the people that voted
".
DeMaio noted
that the "plan would be to trademark a
new design
" at the end of what "will likely be a long process
".
He quoted from the contest announcement, which says:
The person doing the branding changes and maintenance has a say in any changes. The ultimate brand decision will rest with members of the project doing the implementation, but the results from this logo competition will provide an expressed opinion of the brand identity project wide.
But Brown complained
that there was no option to express the "no change" opinion, though
concerns about that were raised "here, on Reddit, and across other
social media platforms
". In addition, he wondered why there was such a
push to do this contest, which is pressuring the branding folks who may or
may not think there is any need to change the logo. He suggested that
"those pushing this logo-change agenda
" step back and process the
feedback they had already been given.
As might be guessed, DeMaio saw
things differently; he suggested waiting for the results of the vote
once it ended (on December 10). It is not clear to him that there is a
groundswell of opposition to changing the logo; he personally thinks that
not making a change may be viewed as stagnation. Some of that post
seemed to have greatly angered Brown, who replied
in a heated fashion—to the point that a new community member called
out "the accusations,
insinuations, and anger
" in the post.
Brown's complaints seem to boil down to his perception that all of the effort that is going into the contest may be entirely wasted if SUSE is not on board with spending the time and money on registering a new trademark. He seems to think that the majority opinion is that the logo should not change and, in truth, it seems to be hard to find many advocates (other than DeMaio) in various places that the subject has arisen. Brown clearly does not believe that DeMaio should be the one driving the effort—if the effort is needed at all—though angrily airing that in public seems counterproductive at best.
Part of Brown's frustration may stem from the lengthy process that resulted
from changes to the openSUSE logo in 2005 and 2007; the paperwork on those
took two and six years respectively as he described
in a Reddit comment. Meanwhile, the prospect of spending money to change
something for change's sake seems like a bad plan to some. Carlos E. R. said: "Change just for
changing? No.
". Board member Patrick Fitzgerald thought
that "money is better spent on events and infrastructure than trademark
lawyers... but having enough for both would be even better ;)
".
At this point, the voting has closed and a new set of logos has been "chosen", though there was a three-way tie for the Tumbleweed logo and, of course, there was no "keep existing" option for the openSUSE logo. Where things go from here is unclear; the holidays approach, for one thing, and DeMaio seemed to indicate he would let others drive the process going forward. But, before any change to LCP's winning openSUSE logo (seen at left) can be effected, the issues around trademark registration will need a resolution of some sort. Then there may be a lengthy wait for the trademark-registration process to play out—if the community, and SUSE, find that to be a worthwhile thing to pursue.
Controlling shadow-stack allocation in clone3()
User-space shadow stacks are a relatively new feature in Linux; support was only added for 6.6, and is limited to the x86 architecture. As support for other architectures (including arm64 and RISC-V) approaches readiness, though, more thought is going into the API for this feature. As a recent discussion on the integration of shadow stacks with the clone3() system call shows, there are still some details to be worked out.A shadow stack is a copy of the current call stack that contains only return addresses; it is maintained by the CPU. While user-space code can access (and even modify) the shadow stack, that access is limited in a number of ways by the hardware. When a shadow stack is enabled, every function call results in the return address being pushed onto both the regular and the shadow stacks. Whenever a function returns, the return address on the regular stack is compared to the copy on the shadow stack; if the two don't match, the processor will trap and (normally) the affected process will be killed. This feature is meant to provide a defense against attacks based on overrunning stack-based variables, including return-oriented programming (ROP) attacks.
There is code that will not work with a shadow stack, so the feature cannot be enabled by default. Thus, when a process is created, it does not have a shadow stack, even on an architecture that supports the feature; a shadow stack can be created and enabled with a prctl() call. If, however, a thread with a shadow stack already set up creates a new thread, the kernel will create and install a shadow stack for that thread before it begins execution; that ensures that the thread will never run without protection. As will be seen, though, there are reasons why a process may want a higher level of control over how that shadow stack is created.
In October, Mark Brown (who is working on the arm64 shadow-stack implementation) posted a patch series adding that control to clone3(), a relatively new system call that was designed to allow the addition of new features in this way. The initial version of the series added two fields to the clone_args structure used to pass parameters to clone3(): the address and size of the shadow stack to be provided to the new thread. Rick Edgecombe (who carried the x86 implementation over the finish line) quickly pointed out a problem with that API, though: the ability to place the shadow stack in memory could be used to put it in an inconvenient location — on top of another shadow stack, for example. Nothing good would come from such an action, and it could be used as an attack vector.
After some discussion, it was concluded that, while it might be useful to allow user space to be able to position the shadow stack exactly, there was no overwhelming need for that capability. So, in subsequent versions of the series (including the current fourth revision), only the size of the desired shadow stack can be provided to clone3(), in a clone_args field called, unsurprisingly, shadow_stack_size. If that size is provided, it will be used by the kernel to create the new thread's shadow stack; otherwise the default size (which is equal to the size of the regular stack) will be used instead.
By version
3, posted in in late November, the patch set appeared to be settling
down. Christian Brauner, though, questioned
whether this API was worth adding, worrying that it was a step toward
turning clone3() (which he created) into "a fancier version of
prctl()
". He wondered why it was necessary to allow user space to
affect the size of the shadow stack at thread-creation time. Recognizing
that he perhaps did not fully understand the problem, he asked a few
questions about the motivations for this change.
One of those motivations is to prevent over-allocation of the shadow stack, which can result from the current policy of allocating the shadow stack with a size equal to that of the regular stack. Szabolcs Nagy explained the problem in this case: if a thread is created with a large (regular) stack, perhaps so that it can store a large array of data there, the shadow stack will be just as large, and almost all of that space will be wasted. For a single thread, perhaps that waste could be tolerated, but in an application with a large number of threads, it could add up to a lot of lost memory.
There is also a case where an equally sized shadow stack could be too small. The sigaltstack() system call allows a thread to set up an alternative stack to be used for signal delivery. Even when a thread is switched to its alternative stack, though, it continues to use the same shadow stack. If the thread exhausts the regular stack, then handles a signal (perhaps even caused by running out of stack space) with a deep call chain on an alternative stack, the shadow stack could overflow.
The kernel can try to make an educated guess as to what the optimal shadow-stack size might be, but it will remain a guess. As Brown pointed out, the only way to improve on that guess is to accept information from user space, which (presumably) has a better idea of what its needs are. Creating a new thread without a shadow stack and letting that thread map one explicitly would be one way to solve the problem; creating a suitably sized shadow stack in clone3(), though, ensures that the new thread will never run without shadow-stack coverage.
Brauner seemed to accept the reasoning behind the addition of this feature to clone3(), but he worried that there is currently only one architecture with shadow-stack support in the mainline currently. The addition of others, he hinted, could drive changes in the proposed API; he suggested keeping the clone3() changes out of the mainline until arm64 support has been merged. Brown was amenable to that plan for now, as long as the arm64 and clone3() changes could be merged together.
That seems likely to be how things will go from here. The merging of arm64 shadow-stack support appears to be on a slow path while the user-space side is being finalized, so it may be a while before all this work lands in a mainline kernel. If all goes well, though, it will eventually be possible to control the size of the shadow stack given to new threads on all architectures that implement shadow stacks.
Modern C for Fedora (and the world)
It can be instructive to pull down the dog-eared copy of the first edition of The C Programming Language that many of us still have on our bookshelves; the language has changed considerably since that book was published. Many "features" of early C have been left behind, usually for good reasons, but there is still a lot of code in the wild that is still using those features. A concerted effort is being made in both the Fedora and GCC communities to fix that old code and enable some new errors in the GCC 14 release (which is in stage 3 of its development cycle and likely to be released by mid-2024), but a fair amount of work remains to be done.There are a number of constructs that were normal in 1980s C, but which are seen as a breeding ground for bugs now. These include:
- Implicit function declarations: if code calls function() without having first included a declaration for that function, the compiler implicitly declares it as taking no parameters and returning an int value. That may not be how the function is actually defined, opening up possibilities for all kinds of confusion.
- Implicit integer declarations: a variable declared with just a storage class (static, for example) is implicitly deemed to be an int. C++ has already adopted type inference, where the compiler figures out what the appropriate type for the variable should be from how it is used, in this case. There are schemes afoot to add a similar feature to C, but type inference is incompatible with implicit int.
- Conversions between pointers and integers: original C played fast and loose with pointer values, allowing them to be converted to and from int values at will. Whether such constructions actually work on current architectures (where a pointer is likely to be 64 bits and an int 32 bits) is a matter of chance.
- Inconsistent return statements: old-style C paid little attention to whether a function returned a value or not; a function declared int could do a bare return (or just fall off the end with no return statement at all), and void functions could attempt to return a value without complaint. Good things will not happen if a function fails to return a value that the caller is expecting.
- Missing parameter types in function definitions: C would accept such definitions, assigning no type to the parameter at all. That means that typos in a function prototype (such as "f(ant)" instead of "f(int)") can give surprising results.
- Assignments between incompatible pointer types: continuing in the "fast and loose with pointers" theme, early C had no objections to assigning a pointer value to an incompatible type without even a cast. Sometimes a developer writing such an assignment knew what they were doing; other times not.
Current GCC versions will issue warnings for the above constructs, but will proceed to compile the code anyway. Florian Weimer, though, would like to change that situation; at the end of November, he posted an update on work toward turning the warnings for obsolete C constructs into hard errors instead. This would seem like a sensible thing to do; those constructs have been deprecated for ages; they can hide bugs or prevent the adoption of new language features and should not be appearing in modern code.
There is only one little problem: a lot of code in the free-software world is not modern. Simply turning all of those warnings into errors has the potential to break the compilation of numerous packages — an outcome that is not likely to be universally welcomed. To address this problem, the Fedora project has been working on a "porting to modern C" project since at least late 2022. The idea is to find the packages in Fedora that fail to build with the new errors and fix them, sending those fixes upstream whenever possible. Once Fedora builds correctly, chances are that the amount of old code that remains will be relatively small.
Weimer has also posted an update on the Fedora work. There are, it seems, still a number of packages (out of about 15,000 tested) that generate errors indicating the presence of old code:
Implicit function definition 53 Implicit integer declaration 2 Integer conversion 99 Return mismatch 13 Missing parameter type 0 Pointer assignment 374
While quite a bit of progress has been made toward the goal of building Fedora with the new errors, Weimer points out that the job is not yet done:
As you can see, the incompatible-pointer-types issues are a bit of a problem. We fixed over 800 packages during the first round, and now it looks like we are only two thirds done.It is unlikely that I will be able to work on all these issues myself or with help from the people around me. I just suggested to GCC upstream that we may have to reconsider including this change in the GCC 14 release.
Weimer included a separate column for programs that may be miscompiled
because autoconf may be confused by the new errors. For example,
many of its checks don't bother to declare exit(); they will fail
to compile if the error for implicit function definitions is enabled,
causing autoconf to conclude that the feature it is checking for
is absent. There are also seemingly problems with the Vala language, which compiles to
obsolete C. Vala has not been under active
development has not addressed this problem for some time
and seems unlikely to be fixed.
The current plan is to continue this work, focusing mostly on the Fedora Rawhide development distribution. Efforts will be made to deal with the autoconf problem and to put some sort of hack into Vala, but that still leaves hundreds of packages needing further attention. If they cannot be fixed in time, it may not be possible to enable all of those errors in the GCC 14 release.
Part of the problem, perhaps, is that it appears to have fallen on Fedora and GCC developers to make these fixes. In many cases, this may be the result of the lack of a viable upstream for many packages; we are probably all using more unmaintained code than we like to think. At its best, this work might shine a light on some of those packages and, in a truly optimistic world, bring out developers who can pick up that maintenance and modernize the code. In many cases, it should be a relatively straightforward task and a reasonable entry point into maintainership. With enough help, perhaps we can finally leave archaic C code behind.
Some recent and notable changes to Rust
The Rust project makes incremental releases every six weeks, a fact that makes it easy to overlook some of the interesting changes coming to the language, such as new ABIs, better debugger support, asynchronous traits, and support for C strings. The end of the year provides an opportunity to look back over the past several months of updates, and to look forward to what to expect in 2024.
Stack unwinding across foreign function interfaces
Rust permits linking with programs written in other languages. This includes both allowing Rust programs to make use of existing libraries with a compatible C interface, and permitting Rust libraries to be used seamlessly in existing projects. This cross-language compatibility is a key part of allowing the incremental adoption of Rust components in larger programs. It also makes it possible to embed even complex libraries such as Lua and SQLite into Rust programs.
Currently, a panic in Rust or an exception from another language, such as C++, that attempts to unwind the stack across the boundary between languages in either direction is undefined behavior. In practice, polyglot programs like this currently work fine, as long as the various object files are all compiled with something like the -fexceptions flag, to tell compilers to compile functions with unwinding support even in languages that don't require it, such as C. However, because unwinding is not officially part of the ABI, that could change at any time if compilers on either side of the boundary change how they format unwinding information.
Exceptions are also not the only use of unwinding. The GNU C Library (glibc) on Linux uses "forced unwinding" to implement pthread_exit() and pthread_cancel(). Unlike on Linux, where glibc implements longjmp() by saving enough stack location information in setjmp() to restore without stack unwinding, Windows also uses forced unwinding to implement longjmp(). The limitations on cross-language unwinding make writing cross-platform wrappers around programs that use these mechanisms impossible without causing undefined behavior.
The FFI-unwind working group is looking to change that by specifying how to permit stack unwinding to cross the barrier between languages.
In Rust 1.71.0, released in July 2023, the FFI-unwind working group took the first step toward specifying the behavior by adding -unwind variants of the existing ABI specifiers for external functions. Programs in Rust can specify which ABI-specific external functions (or functions from libraries the program expects to link with) should be compiled for, including options such as "C", "stdcall", or "fastcall". The new -unwind variants behave identically for now, but will be updated with new guarantees to keep behavior consistent when the FFI-unwind working group finishes its task. Programmers looking to write future-proof library code can begin using the -unwind ABI specifiers now:
// C function called from Rust #[link(name = "c_library")] extern "C-unwind" { fn function_to_link_against(); } // Rust function callable from C #[no_mangle] pub extern "C-unwind" fn callable_from_c() { // Function body here }
Embedded debugger visualization information
Rust 1.71.0 also includes support for embedding GDB visualization scripts or Windows Natvis descriptions directly into Rust libraries. These scripts allow hooking into GDB's or Natvis's printing code to provide custom visualizations for data types defined by the library. The standard library already ships with debugger visualization scripts for the core data types, but this change makes that ability available to user code as well.
As GDB doesn't automatically load scripts from binaries being debugged, one must add the object file containing the Rust library in question to GDB's auto-load safe-path in order to benefit from included visualization scripts.
Improvements to thread-local initialization
Rust 1.73.0, released in October 2023, introduced convenience features to make thread-local initialization easier. Thread-local values in Rust are represented by a data type called a LocalKey. In the past, accessing the value stored inside a LocalKey required using a callback. LocalKey objects storing contents with interior mutability now support get(), set(), take(), and replace() methods to modify or retrieve the contained value directly.
Captured lifetimes in opaque types and asynchronous trait methods
The ability to write asynchronous trait methods is a commonly requested feature because it would allow for less coupling between components in asynchronous programs. For example, it would permit defining asynchronous destructors that would simplify resource handling in asynchronous programs. This was a key goal of the async working group for this year. As part of continuing work to make this possible, Rust 1.74.0, released in November 2023, resolved a longstanding issue that prevented trait methods with opaque return types from implicitly capturing lifetime information. This was the last remaining hurdle in the way of permitting asynchronous trait methods.
There have been libraries (such as async_trait) that work around this issue to provide asynchronous trait methods for many years, but those libraries were not considered appropriate for inclusion in the standard library because they rely on introducing an additional layer of dynamic dispatch. The Rust project says that "abstraction without overhead" is a key pillar of Rust's design, including a guarantee that traits can be statically dispatched when the types are known at compile time.
To see why fixing this issue was necessary to this goal, it is perhaps helpful to define some terms. The issue was that trait methods with opaque return types were not able to implicitly capture lifetime information. Capturing lifetime information refers to creating a type such that the type's validity depends on the lifetime information in some way. When the type contains references to borrowed data that exists elsewhere, it needs information specifying how long the referenced data is valid so that the Rust compiler can perform its static lifetime analysis and guarantee that the referenced data is valid longer than the returned type is used.
For example, a variable defined as:
Wrapper<'a>
that contains a field like:
&'a Foo
must live for a shorter time than the lifetime it captured ('a) in order to statically guarantee that no use-after-free occurs.
Explicit lifetime information, such as a lifetime bound given in the declaration of the trait, was accepted in previous versions of the language. When Rust compiles an asynchronous function, however, the compiler causes it to return an anonymous, compiler-generated structure that implements the Future trait. This structure can capture lifetime information from variables defined in the body of the method, not present in the declaration of the method in the trait, and therefore not named explicitly in the return type.
An opaque return type (sometimes also called -> impl Trait) refers to Rust's support for writing functions or methods that return some concrete type that implements an interface, without saying what that type is. This includes the anonymous structures representing an asynchronous function. Trait methods with opaque return types allow the compiler to return the anonymous structure directly, without wrapping it in a heap allocation with a vtable, the way that existing libraries do.
In Rust 1.73.0, it was possible to write an ordinary function that returned an opaque type without any wrapping, but writing the same thing as a trait method was not possible. Now that this prerequisite has been completed, Rust 1.75.0 (the upcoming release, due in late December) will include support for declaring asynchronous methods in traits, with syntax that looks like this:
trait Trait { async fn method(self) -> usize; } impl Trait for Foo { async fn method(self) -> usize { // Function body here } }
This is equivalent to defining the trait method as returning an impl Future<Output = usize>.
With this work done, the async working group is looking to add support for asynchronous iterators in 2024, and asynchronous destructors by 2027. This also unlocks work on the keyword generics initiative that seeks to make it possible to write library code that abstracts over whether I/O operations are to be done asynchronously.
Other upcoming changes
Rust 1.75.0 will also include additional intrinsic functions from the ratified RISC-V extensions. These include atomic instructions and scalar cryptography instructions.
Rust 1.75.0 will be the last release to support Windows 7, 8, and 8.1. Starting with Rust 1.76.0 in February, only Windows 10 and later will be supported as a tier-1 target — one for which official binary releases are made available, and that have automated testing to ensure they remain stable. Older Windows versions will remain as a "legacy" target, but will not receive guaranteed support.
Rust 1.76.0 will also bring support for C-style, null-terminated, string literals, to make writing code that communicates with C less painful. Unlike normal Rust string literals, which uniformly contain only normalized UTF-8 data, these literals will be able to contain data in encodings other than UTF-8, the way that bytestring literals can. The new string literals will automatically add null terminators. They are written c"...", and provide a replacement for the various C-string macros that exist across the Rust ecosystem.
Rust 2024 Edition
The next edition of Rust is planned for 2024. Editions are Rust's solution to permitting backward-incompatible syntax changes without fracturing the language. Crates written in different editions of Rust are still interoperable, but editions allow crates to individually opt-in to new features.
It is not yet clear what else will make it into the 2024 edition, or when exactly next year the 2024 edition will be released. However, the Rust project's stated goals for the 2024 edition include a continuing focus on developer ergonomics, especially around writing asynchronous code. We already know that the edition is expected to include new syntax for creating generators, and the Future and IntoFuture traits becoming part of the standard library prelude (the set of functions, types, and traits available to all Rust programs without imports).
Rust has come a long way since the 1.0 release in 2015, but it continues to accumulate new and exciting features. While the improvements to asynchronous code will almost certainly be the headline feature of the 2024 edition, there are many smaller features that also contribute to the continued growth of the language.
Project Bluefin: A customized Fedora Silverblue desktop image
So-called "immutable" Linux distributions have been in development for some time, but (unless you count ChromeOS) haven't gained much traction. Project Bluefin, is a heavily customized set of Fedora Silverblue images coming from the Universal Blue community; they are designed to deliver a reliable Linux desktop that's as easy to use as a Chromebook but more customizable. Bluefin's mission is to change up the desktop experience and attract a new generation of open-source contributors with a "cloud-native" take on developing and delivering the operating system.
Fedora Silverblue
LWN readers are, no doubt, at least passingly familiar with Fedora but may not know about the Silverblue project. A variant of Fedora Workstation, Fedora Silverblue has the same stock GNOME desktop and base software, but delivered as an immutable image. It's built using OSTree and rpm-ostree, which are a set of tools that provide a Git-like way of working with OS images created from RPMs. OSTree deploys a bootable, read-only filesystem tree with writable content stored in /etc and /var; user home directories live under /var/home.
Rather than installing or updating Fedora package by package, Silverblue installs an image built from RPMs (with rpm-ostree) as a single transaction. When updates are applied, they're also applied as a single transaction, whether it includes an update to a single RPM or an upgrade to a new release of Fedora. For example, one can update from Fedora Silverblue 37 to 38 by rebasing on the Silverblue 38 image.
The OSTree project got its start with gnome-continuous, a research project aimed at making it easier to take GNOME's Git repositories, build them, and make them available quickly for testing. Later, OSTree and rpm-ostree were used as part of Project Atomic to develop immutable variants of Fedora, CentOS, and Red Hat Enterprise Linux to run Linux containers. The technology lives on in the CoreOS family and is also being used for Silverblue.
This model has at least three distinct benefits. First, systems deployed in this way are identical. That is, using the OSTree model, every system's install using the same image will have the same versions of software without any package variations. This can help prevent "drift", where some systems running package-based installs may have slightly different versions of software or missing packages, etc. Updates are staged in the background and take effect on reboot rather than updating packages on a running system.
A second major benefit is the ability to roll back updates if needed. Let's say one has taken their laptop on the road for an event and updated the system to the latest Fedora release, but the update has a bug that disables the laptop's WiFi. Reversing this using the standard RPM model could be challenging, to say the least. Under the OSTree/rpm-ostree model, one need only revert back to the prior, working, image and report the bug like a good community citizen.
The third advantage is the ability for adventurous users to easily switch between multiple branches of OSTree images, which is called "rebasing". By using "rpm-ostree rebase", one can pull a major operating system update and move from Fedora 37 to 38, or perhaps move between Fedora Silverblue and a custom image like Bluefin (and back again, as well).
Application management on immutable systems
The image-based approach has its clear positives, but also raises the question of how users can add software to their systems. This lack of flexibility was a feature for the Atomic Host and CoreOS use case — the host operating system was not meant to be customized, it was simply a vehicle for running Linux containers.
Systems built with rpm-ostree are "immutable" with an asterisk. Users can install software from RPMs by layering packages on top of the image with "rpm-ostree install packagename". This is usually done to add drivers, libraries, or system software, rather than for user-facing software. This creates a new image and it's generally recommended to reboot to apply the changes — though rpm-ostree does allow users to apply changes to a live environment with the apply-live option.
But that mechanism is meant to make changes and updates to the image and then roll it out to the fleet, rather than tending to systems individually, so each host offers only the needed software to boot the system and run application containers. Customizing individual hosts is generally considered an anti-pattern when running container workloads at scale.
However, a desktop operating system without the ability to install new applications would be of little interest to most users. Silverblue addresses this in several ways. Users are guided to using Flatpak for GUI desktop applications. If an application isn't included in the default selection, then one can turn to Flathub to it. The Flatpak format has its pros and cons, along with fans and detractors. Needless to say, users who do not like the format would be unlikely to find happiness with Silverblue or Bluefin.
Silverblue also offers a utility called Toolbox (sometimes stylized as Toolbx), which is a streamlined way of using privileged Linux containers to install command-line tools and utilities. Toolbox uses Open Container Image (OCI) images, but integrates them into the system so that they provide access to the host as if they were natively installed. Toolbox containers can access Wayland/X11, removable devices, the systemd journal, and other components. This is useful for troubleshooting, setting up a development environment, or otherwise installing software that might not be available as a Flatpak or well-suited to the Flatpak model.
Universal Blue and Project Bluefin
Bluefin is one of many images created by the Universal Blue project, which starts with Fedora Silverblue and then diverges from stock Fedora to create a number of customized images for various desktops and specific use cases. The images offer additional packages, custom udev rules, codecs that Fedora will not ship, as well as things like NVIDIA drivers and customizations for popular hardware. These images promise a better "out of the box" experience for those who have a target device and want to avoid fussing with driver installations and customizations. Conversely, this may be off-putting for users who wish to avoid any proprietary drivers or codecs.
The most recent addition to Universal Blue's family of images is Bluefin, currently described as "beta" with an eye toward a stable release in the Fedora 40 time frame. Bluefin is a take on Silverblue that aims to have an Ubuntu-like look and feel, with a dock (the Dash to Dock extension) and AppIndicators out of the box, plus the aforementioned codecs, defaulting to Flathub for Flatpaks, and other customizations. Flatseal is also installed by default, should users wish to view or modify the permissions granted to Flatpak applications. See the screen shot below to get a feel for Bluefin's default look.
Bluefin also prefers Distrobox to Fedora's Toolbox for providing a mutable environment or environments. Distrobox aims to let users run any Linux distribution inside their terminal. Like Toolbox, Distrobox containers are privileged and integrate almost seamlessly with the user's desktop environment. Users can use "distrobox create debian", for instance, to create a containerized environment based on the default Debian Docker (OCI) image. This can be immensely useful for testing and experimentation using the user space for multiple Linux distributions without having to maintain multiple physical or virtual machines, or dual-booting.
The Bluefin Developer images are particularly interesting. In addition to the default customizations, the Developer images include tools like DevPod, Devbox, Fleek, and Incus. DevPod is similar to GitHub Codespaces, a tool to create and manage "run anywhere" developer environments. Likewise, Devbox uses Nix under the hood to create reproducible developer environments — but abstracts away some of the complexity of Nix. Fleek also gives users a way to use Nix under the hood to configure their working environment and even make it portable and reproducible. Incus is a recent fork of Canonical's LXD, and is designed to run virtual machines via QEMU, and/or system containers via LXC.
In short, Bluefin Developer comes heavily loaded with leading-edge developer tools that are popular (or aim to be) with developers in and around the "cloud-native" space. It wouldn't be a bad thing if a project like Bluefin were to lure some cloud-native developers away from macOS to (or back to) the Linux desktop. Note that work is also afoot to generate Bluefin images based on the Fedora Asahi Special Interest Group (SIG) to support Macs using Apple silicon.
The Bluefin experience
Installing Bluefin is much like installing Fedora, with a few extra steps. One can pick the Bluefin ISO image or rebase to a Bluefin image from Fedora Silverblue. The process takes a bit longer than a standard Fedora install — particularly since Bluefin installs several packages from Flathub post-install and rebasing to a new image after installation takes additional download time.
Note that, as of this writing, Bluefin does not support manual partitioning or dual-booting setups — so interested users will want to have a dedicated machine or test Bluefin in a virtual machine until that capability is available. For this article, Bluefin was installed on a Lenovo Thinkpad X280 with 16GB of RAM and 256GB of storage, as well as in a virtual machine with similar RAM and 4 vCPUs. Performance on the Thinkpad is similar to stock Fedora Workstation, perfectly acceptable for light work, battery use was about the same, waking from sleep worked without fail, and no hardware problems came to light.
The look and feel of the Bluefin Developer desktop differs from the stock Fedora Workstation by providing a dock out of the box, Alt-Tab cycles through open windows rather than applications, windows have the minimize and maximize buttons available by default, and other minor touches like adding the "Blur my Shell" GNOME extension that adds visual effects like "blurring" the dock or top panel with the desktop background so they appear to show through. Whether these are appealing or not is, of course, strictly a matter of taste. I found it necessary to crank down the huge default terminal font size, despite my aging eyes — but otherwise found the look and feel pleasing. Which is not to say that stock Fedora is unpleasing, just that Bluefin's overall look and feel is even more to my taste.
The focus on current developer tools and advanced-user productivity applications sets it apart from Fedora Workstation's more conservative set of default software. Distrobox neatly solves the problem of choosing a Linux distribution by making it relatively easy to set up a variety of distribution environments. With a few commands it was easy to run AlmaLinux 9, Debian 12, and Ubuntu 22.04 LTS environments.
Bluefin also provides just, which is a utility to run project-specific commands from justfiles (like makefiles, but with a simpler syntax). Bluefin ships with a full complement of pre-defined just tasks for everything from showing the changelog between the current system and pending updates, cleaning the system of old containers, or switching the default shell to zsh or fish. The "just update" task helps in keeping a Bluefin system up-to-date by automating updates for the OS image, installed Flatpaks, and likely one or more Distrobox environments. Those working with containers for development work have the option of using Podman or Docker. Running "just docker" will fire up the Docker service and add the user to the docker group, for example.
One minor complaint about the "rpm-ostree update" operation if run separately is that it does not offer status updates while working. It provides a report of how many layers are present and need to be downloaded, with an estimation of size and begins working without any indication of progress until it has downloaded its new data. Then it provides a report of changes and guides the user to perform a "systemctl reboot" to make them take effect. It's easy to wonder whether an operation has stalled or if it's just being extremely quiet. The answer is "quiet", apparently. Switching from a Fedora 38 base to Fedora 39, then back again, worked without hiccup.
Final thoughts
Though Bluefin is considered beta status, I had no problems using it as a daily driver. Overall, Bluefin seems like a fine choice for a desktop distribution even considering that status. That is not surprising, since it's building on a fairly mature base with Fedora Silverblue. It comes with something of a learning curve for those accustomed to Fedora Linux, but not so great that it's off-putting.
Bluefin is especially interesting because it leans heavily into the Fedora tenets of "features" and "first." Aside from its immutable model and additional polish for specific systems, it serves as a showcase for a lot of recent tools like Devbox, Distrobox, just, and many more. For users already familiar with these tools, it's handy to have them all in one basket. For others, trying out Bluefin and exploring the varied development tools and utilities may lead to some useful discoveries. Putting Bluefin through its paces is a great way to uncover newer open-source tools that aren't yet well-known.
For those interested in taking the plunge, ISOs are listed on GitHub and the Universal Blue documentation will help to get started.
Page editor: Jonathan Corbet
Next page:
Brief items>>