Recent Posts
Archives

PostHeaderIcon [DefCon32] From Getting JTAG on the iPhone 15 to Hacking Apple’s USB-C Controller

Thomas Roth, known as Stacksmashing, a hardware security researcher and co-founder of HexHive, takes the audience on a technical odyssey to compromise Apple’s proprietary ACE3 USB-C controller in the iPhone 15. Using reverse-engineering, RF side-channel analysis, and electromagnetic fault injection, Thomas achieves code execution on this custom chip, unlocking JTAG access and exposing its inner workings. His work highlights the persistence required to tackle secure hardware.

Cracking the ACE3 Controller

Thomas introduces the ACE3, a microcontroller managing USB-C functions, internal buses, and JTAG on the iPhone 15. Unlike its predecessor, ACE2, which was vulnerable to software exploits, ACE3 employs personalized firmware and disabled debug interfaces. Through meticulous reverse-engineering, Thomas mapped its architecture, revealing access to UART and SPMI buses, critical for deeper device exploration.

Leveraging Side-Channel Attacks

To bypass Apple’s protections, Thomas employed RF side-channel analysis and electromagnetic fault injection. Using tools like ChipShouter, he induced faults to read and write arbitrary memory, dumping the ACE3’s ROM and RAM. This painstaking process, despite battery challenges, yielded a complete firmware dump, enabling further analysis of the chip’s security mechanisms.

Democratizing Hardware Hacking

Recognizing the high cost of professional tools, Thomas ported his attack to the affordable PQM-P1, reducing the cost to $60. This democratization of fault injection empowers researchers to explore similar chips without prohibitive expenses. His open-source firmware for ChipShouter automates the process, making hardware security research more accessible to the community.

Future of Hardware Security Research

Thomas concludes by encouraging researchers to persist in analyzing unknown silicon, as demonstrated by his success with minimal prior knowledge. His glitching lab at the MedSec Systems Village invites hands-on exploration of fault injection techniques. By sharing his tools and methodologies, Thomas fosters a collaborative approach to uncovering vulnerabilities in secure hardware.

Links:

PostHeaderIcon 🚀 Mastering Flyway Migrations with Maven

Managing database migrations can be tricky, but Flyway simplifies the process with versioned scripts for schema changes, data updates, and rollbacks.
Here’s a quick guide with useful examples:

✅ 1️⃣ Creating a Table (V1 – Initial migration)
“`
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(50)…,
email VARCHAR(100)…,
created_at TIMESTAMP…
);
“`
✅ 2️⃣ Inserting Sample Data (V2)
“`
INSERT INTO users (username, email) VALUES
(‘alice’, ‘alice@example.com‘),
(‘bob’, ‘bob@…’);
“`

✅ 3️⃣ Adding a New Column (V3 – Schema change)
“`
ALTER TABLE users ADD COLUMN last_login TIMESTAMP;
“`

✅ 4️⃣ Renaming a Column (V4)
“`
ALTER TABLE users RENAME COLUMN email TO contact;
“`

♻ Undo Script (U4__Rename_email_to_contact.sql)
“`
ALTER TABLE users RENAME COLUMN contact TO email;
“`

✅ 5️⃣ Deleting a Column (V5)
“`
ALTER TABLE users DROP COLUMN last_login;
“`

♻ Undo Script (U5__Revert_remove_last_login.sql)
“`
ALTER TABLE users ADD COLUMN last_login TIMESTAMP;
“`

✅ 6️⃣ Deleting Specific Data (V6)
“`
DELETE FROM users WHERE username = ‘alice’;
“`

♻ Undo Script (U6__Revert_delete_user.sql)
“`
INSERT INTO users (username, contact) VALUES (‘alice’, ‘alice@example.com‘);
“`

💡 Configuring Flyway in pom.xml
To integrate Flyway into your Spring Boot or Java project, add the following configuration in your `pom.xml`:
“`
<properties>
<flyway.version>11.4.1</flyway.version>
</properties>

<dependencies>
<!– Flyway Core –>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<version>${flyway.version}</version>
</dependency>

<!– Database Driver (Example: PostgreSQL) –>
<dependency>
“org.postgresql:postgresql:runtime”
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-maven-plugin</artifactId>
<version>${flyway.version}</version>
<configuration>
<url>jdbc:postgresql://localhost:5432/mydb</url>
<user>myuser</user>
<password>mypassword</password>
<schemas>public</schemas>
<locations>filesystem:src/main/resources/db/migration</locations>
</configuration>
</plugin>
</plugins>
</build>
“`

📂 Migration scripts should be placed in:
`src/main/resources/db/migration/`
Example:
“`
V1__Create_users_table.sql
V2__Insert_sample_data.sql
V3__Add_last_login_column.sql
“`

💡 Flyway Maven Plugin Commands
👉Apply migrations:
“`mvn flyway:migrate“`
👉Undo the last migration (if `flyway.licenseKey` is provided):
“`mvn flyway:undo“`
👉Check migration status:
“`mvn flyway:info“`
👉Repair migration history:
“`mvn flyway:repair“`
Clean database (⚠Deletes all tables!):
“`mvn flyway:clean“`

PostHeaderIcon 🚀 Making Spring AOP Work with Struts 2: A Powerful Combination! 🚀

Spring AOP (Aspect-Oriented Programming) and Struts 2 might seem like an unusual pairing, but when configured correctly, they can bring cleaner, more modular, and reusable code to your Struts-based applications.

The Challenge:

  • Struts 2 manages its own action instances for each request, while Spring’s AOP relies on proxying beans managed by the Spring container. This means Struts actions are not Spring beans by default, making AOP trickier to apply.
  • The Solution: Making Struts 2 Actions Spring-Managed
  • To make Spring AOP work with Struts 2, follow these steps:


✅ Step 1: Enable Spring integration with Struts 2


Ensure your `struts.xml` is configured to use Spring:

“`<constant name=”struts.objectFactory” value=”spring”/>“`

This makes Struts retrieve action instances from the Spring context instead of creating them directly.

✅ Step 2: Define Actions as Spring Beans


In your applicationContext.xml or equivalent Spring configuration, define your Struts actions:

“`
<bean id=”myAction” class=”com.example.MyStrutsAction” scope=”prototype”/>
“`
Setting the scope to “prototype” ensures a new instance per request, preserving Struts 2 behavior.


✅ Step 3: Apply AOP with `@Aspect`

Now, you can apply Spring AOP to your Struts actions just like any other Spring-managed bean:

“`
@Aspect
@Component
public class LoggingAspect {

@Before(“execution(* com.example.MyStrutsAction.execute(..))”)
public void logBefore(JoinPoint joinPoint) {
System.out.println(“Executing: ” + joinPoint.getSignature().toShortString());
}
}
“`

This will log method executions before any `execute()` method in your actions runs!

Key Benefits of This Approach
🔹 Separation of Concerns – Keep logging, security, and transaction management outside your action classes.
🔹 Reusability – Apply cross-cutting concerns like security or caching without modifying Struts actions.
🔹 Spring’s Full Power – Leverage dependency injection and other Spring features within your Struts 2 actions.
🔥 By integrating Spring AOP with Struts 2, you get the best of both worlds: Struts’ flexible request handling and Spring’s powerful aspect-oriented capabilities. Ready to make your legacy Struts 2 app cleaner and more maintainable? Let’s discuss!

PostHeaderIcon [OxidizeConf2024] How Rust Makes Oxide Possible

Building a Cohesive Cloud Computer

Oxide Computer Company is redefining server hardware by designing a cohesive system from the ground up, leveraging Rust’s capabilities across its stack. Steve Klabnik, a prominent figure in the Rust community, delivered an insightful talk at OxidizeConf2024, detailing how Rust enables Oxide’s ambitious vision. From firmware to the host operating system and HTTP API backend, Steve highlighted Rust’s role in creating a robust, open-source server platform that contrasts with traditional fragmented server designs.

Oxide’s “cloud computer” integrates hardware and software, offering customers a rack-scale solution managed via an EC2-like API. Steve, drawing from his experience at Sun and Apple, emphasized the company’s commitment to open-source principles, making nearly all code publicly available. This approach not only fosters transparency but also allows the community to benefit from Oxide’s innovations, such as custom service processors that replace traditional baseboard management controllers (BMCs).

Rust in Low-Level Systems

Rust’s suitability for low-level tasks like firmware and operating systems is a cornerstone of Oxide’s strategy. Steve explained how Rust’s memory safety and type system prevent common errors in safety-critical components, such as the root of trust and service processor firmware. Unlike traditional C-based firmware, Rust’s borrow checker ensures robust code, reducing debugging time. This was evident in Oxide’s development board, which uses an ARM chip and custom firmware written in Rust, showcasing the language’s ability to handle complex initialization without relying on standard libraries.

However, Steve acknowledged challenges, such as the difficulty of system bring-up, where hardware-specific issues like bit-banging persist regardless of the programming language. Rust mitigates some risks by preventing accidental errors, but developers must still navigate low-level complexities. Oxide’s approach, combining Rust’s safety with rigorous testing, ensures that firmware and operating system components meet the high reliability standards required for server environments.

Enhancing Productivity and Security

A significant advantage of Rust, as Steve articulated, is its impact on developer productivity. Contrary to perceptions of slow iteration due to the borrow checker, experienced Rust developers find it accelerates development by catching errors early. Steve contrasted this with languages like Ruby, where initial coding is fast but debugging consumes significant time. Rust’s holistic cost of ownership—factoring in maintenance and debugging—makes it highly productive, a point often underappreciated by newcomers.

Security is another critical focus, particularly for Oxide’s government and enterprise clients. Steve discussed measures like running Cargo audit to mitigate supply chain attacks, ensuring that the firmware executed matches the intended code. While not foolproof, these practices, combined with Rust’s safety guarantees, provide a robust foundation for secure systems. As Oxide targets regulated industries, Steve anticipates further enhancements to address stringent security requirements.

Community and Future Directions

Oxide’s commitment to open-source extends beyond code to community engagement. Steve highlighted customers like Shopify and a U.S. Department of Energy lab, reflecting diverse use cases from tech companies to privacy-focused organizations. By sharing code and collaborating on platforms like GitHub, Oxide fosters innovation and reduces development costs for non-differentiating components, such as math libraries. This aligns with industry trends toward collaborative open-source solutions, similar to the adoption of Kubernetes in the 2000s.

Looking forward, Steve envisions Rust playing a larger role in server development, particularly as organizations prioritize data sovereignty and cost efficiency. Oxide’s success in selling to high-profile clients demonstrates Rust’s readiness for production environments, challenging the notion that it’s solely suited for high-level applications. By continuing to refine their processes and share insights, Oxide is poised to shape the future of systems programming with Rust.

Links:

PostHeaderIcon [KotlinConf2024] Compose UI on a Light Switch: KotlinConf2024’s Embedded Adventure

At KotlinConf2024, Jake Wharton, a Kotlin enthusiast, shared his journey of running Compose UI on a smart light switch powered by embedded Linux. Sparked by a friend’s discovery of a backdoor granting root access, Jake transformed a $50 Amazon switch into a custom platform. He navigated challenges from setting up a JVM and Kotlin Native to configuring a graphics stack with Skia and handling touch inputs, culminating in a live demo of a touchable UI controlling the switch’s relay, showcasing Compose UI’s multiplatform potential.

A Side Project Ignited by a Backdoor

Jake’s project began in January 2023 when a friend revealed a smart switch’s ADB server allowed password-free root access, discovered via Home Assistant forums. The Decora-style switch, with a touchscreen, light sensor, and microphone, ran a stripped-down Linux. Jake snipped an extension cord to power it and confirmed root access via ADB, setting the stage for custom software. This accessibility, though later patched by the manufacturer, fueled his ambition to replace the Flutter-based UI with Compose UI, blending curiosity with technical challenge.

Bootstrapping the JVM and Kotlin Native

To run Compose UI, Jake first tested the JVM on the switch’s limited 150MB storage. He pushed a Linux ARM JRE and a Java “Hello World” class, confirming JVM functionality. Next, he compiled a Kotlin Native “Hello World” for Linux ARM, proving Kotlin could run natively. These steps established a foundation, but the switch’s constraints—no compiler, minimal storage—required compiling on a host machine and transferring binaries, a process complicated by the need to match the device’s library versions, like libDRM 2.4.

Wrestling with the Graphics Stack

Rendering Compose UI required a graphics stack, but the switch’s Flutter app used Linux’s Direct Rendering Manager (DRM) with OpenGL ES, not OpenGL. Jake’s initial attempt to use Compose UI Desktop failed due to missing OpenGL and X11 dependencies. He ported C-based DRM code to Kotlin Native, meticulously matching the switch’s libDRM version (2.4.87) by analyzing binaries. This process, spanning months, involved pulling headers and shared objects from the device, ensuring compatibility, and overcoming compilation hurdles, like missing toolchains for Linux ARM.

Skia: Rendering Pixels with Kotlin

To draw pixels, Jake turned to Skia, the graphics library powering Flutter and Android. JetBrains’ Skiko library, binding Skia to Kotlin, lacked Linux ARM support for OpenGL ES. Jake forked Skiko, modifying it to use EGL, and endured weeks of GitHub Actions builds to compile Skia for ARM. He integrated Skiko’s C++ and Kotlin bindings, enabling rendering of basic shapes (e.g., red backgrounds, blue circles). This step, though painful, proved Kotlin Native could handle the switch’s display, setting the stage for Compose UI.

Handling Touch Inputs

Interactivity required processing touchscreen inputs. Jake used Linux’s evtest to identify the switch’s touchscreen events, capturing X/Y positions and touch states (down/up). He implemented a single-pointer event parser in Kotlin Native, mapping raw events to a data class within the render loop. This avoided multi-touch complexity, as the switch supported only one pointer. By feeding these events to Compose UI, Jake enabled touch interactions, like button ripples, transforming the switch into a responsive interface, despite occasional crashes during live demos.

Bringing Compose UI to Life

Integrating Compose UI was surprisingly straightforward compared to prior challenges. Jake forked JetBrains’ Compose UI repository, adding a Linux ARM target and implementing expect/actual declarations for dependencies like input handling and scrolling. He replaced low-level Skiko calls with Compose UI’s entry point, passing a Skia canvas and touch events. Initial text rendering crashed due to font issues, but a simple button worked, displaying ripples on touch. Jake’s UI, controlling the switch’s relay via a Unix domain socket and Ktor, demonstrated Compose UI’s adaptability, running the same code on desktop for development.

Demo and Future Potential

Jake’s live demo, despite power converter issues, showcased a draggable switch UI toggling a light bulb, built with Compose UI and Kotlin multiplatform. An enclosure housed the switch, highlighting its real-world application. He envisioned future enhancements, like Home Assistant integration or music control, noting the project’s infancy. Community contributions, like his friend Eric’s interactive component, and public forks for Skiko and Ktor, underscore the project’s collaborative spirit. Jake cautioned against internet-connecting the switch due to security concerns, urging manufacturers to simplify hacking for innovation.

Links:

PostHeaderIcon [DefCon32] Behind Enemy Lines: Going Undercover to Breach LockBit Ransomware Op

Jon DiMaggio, a cyber threat intelligence expert at Analyst1, shares a gripping account of his two-year infiltration of the LockBit ransomware gang. By earning the trust of its leader, LockBitSupp, Jon unmasked the real-world identity of this elusive figure, disrupting the syndicate’s operations. His narrative blends human intelligence with cyber expertise, offering a masterclass in combating ransomware through undercover operations.

Infiltrating the LockBit Syndicate

Jon recounts his covert journey, posing as a potential affiliate to gain access to LockBit’s inner circle. Through strategic exchanges, he built rapport with LockBitSupp, navigating the dark web’s complexities. His interactions, documented in real-time, reveal the gang’s operational structure and reliance on affiliate hackers, providing a rare glimpse into the ransomware ecosystem.

Unmasking LockBitSupp

A pivotal achievement was identifying LockBitSupp’s real-world identity, a feat that challenged the narrative of his anonymity. Jon’s persistence, despite threats of retaliation, led to public exposure via an indictment, disrupting LockBit’s operations. He emphasizes that this identification required meticulous intelligence work, countering claims that LockBitSupp’s operational security was amateurish.

Impact on Ransomware Operations

Jon’s actions had unintended consequences, including a perceived breach that rattled LockBit’s infrastructure. By leaking sensitive communications, he forced the gang to divert resources, weakening their operational resilience. His work with law enforcement amplified this impact, showcasing the power of combining human intelligence with technical analysis to dismantle cybercrime networks.

Lessons for Combating Cybercrime

Concluding, Jon advocates for integrating human intelligence into cybersecurity strategies. His experience underscores the value of undercover operations in understanding and disrupting threat actors. By sharing his methodologies, Jon inspires researchers to adopt similar tactics, strengthening defenses against ransomware and safeguarding organizations worldwide.

Links:

PostHeaderIcon [DefCon32] The Pwnie Awards

The Pwnie Awards, a cornerstone of DEF CON, celebrate the triumphs and missteps of the cybersecurity community with a blend of reverence and humor. Hosted by Ian Roose, this annual ceremony honors groundbreaking research and notable blunders, judged by a panel of esteemed security experts. The 2024 edition, sponsored by Margin Research, Red Balloon Security, and Summercon Foundation, pays tribute to luminaries like Sophia d’Antoine, whose lifetime achievements have shaped the field.

Celebrating Cybersecurity Excellence

Ian opens the ceremony by highlighting its role in recognizing outstanding contributions. The Pwnies showcase the best exploits and research, voted on by peers, offering a unique platform for hackers to gain recognition. From novel vulnerabilities to innovative defenses, the awards reflect the community’s ingenuity, fostering a culture of excellence and accountability in cybersecurity.

Honoring Sophia d’Antoine’s Legacy

A poignant moment comes with the Lifetime Achievement Award for Sophia d’Antoine, accepted by her sister Claudia d’Antoine of Margin Research. Sophia’s work, spanning hacking, policy advocacy, and training with Binary Ninja, left an indelible mark. Ian emphasizes her ethos of curiosity and community care, inspiring attendees to continue her legacy of impactful research and collaboration.

Acknowledging Community Contributions

The ceremony acknowledges the broader community, including nominees, presenters, and sponsors like Margin Research and Red Balloon Security. Ian highlights the collective effort behind the event, from organizers like Neil Durkin and Mark Trumpour to the audience’s participation. This collaborative spirit underscores the Pwnies’ role in uniting hackers to advance the field through shared knowledge and recognition.

Looking Forward to Future Impact

Closing, Ian reflects on the Pwnies’ role in inspiring future research. By celebrating both successes and failures, the awards encourage resilience and innovation. The call to stay curious, inspired by Sophia, resonates as a guiding principle, urging attendees to push boundaries and strengthen cybersecurity through collective effort.

Links:

PostHeaderIcon [DefCon32] The XZ Backdoor Story: The Undercover Op That Set the Internet on Fire

In a riveting exploration of one of the most sophisticated cyberattacks in recent history, Thomas Roccia, a security researcher at Microsoft, unravels the intricate tale of the XZ backdoor. Discovered by Andres Freund in March 2024, this clandestine operation compromised the open-source XZ utility, specifically its liblzma library, threatening SSH servers worldwide. Thomas’s narrative dissects the attacker’s methods, the discovery’s serendipity, and the broader implications for open-source security, urging the community to remain vigilant.

The Discovery of the XZ Backdoor

Thomas begins by recounting the fortuitous discovery by Andres Freund, a Microsoft engineer, who noticed anomalies in the XZ utility. The backdoor, orchestrated by a mysterious maintainer named Jia Tan, was embedded in the liblzma library, a critical component for SSH operations. This breach could have granted attackers remote access to countless systems. Thomas highlights the mix of luck and expertise that led to the detection, emphasizing how close the internet came to a catastrophic compromise.

The Attacker’s Modus Operandi

Delving into the operation, Thomas outlines how Jia Tan infiltrated the XZ project by gaining trust over time. The attacker, potentially backed by a nation-state, employed sophisticated techniques to insert malicious code, exploiting the project’s open-source nature. By meticulously integrating the backdoor into legitimate updates, Jia Tan evaded scrutiny until Freund’s investigation. Thomas details the technical mechanics, including how the backdoor manipulated SSH authentication, underscoring the attacker’s deep understanding of Linux systems.

Lessons for Open-Source Security

The XZ incident exposes vulnerabilities in open-source ecosystems, where trust in contributors is paramount. Thomas advocates for enhanced vetting processes and automated code analysis to detect anomalies early. He stresses the importance of community awareness, as knowledge of such attacks is a key defense. The incident redefines what constitutes a sophisticated attacker, prompting a reevaluation of how open-source projects manage contributions and verify integrity.

Future Vigilance and Community Action

Concluding, Thomas poses a haunting question: how many other Jia Tans are embedding backdoors in open-source projects? He urges researchers to study the XZ case, leveraging blogs and technical write-ups from contributors like Freund. By fostering a culture of transparency and collaboration, the community can bolster defenses, ensuring that open-source software remains a pillar of trust rather than a vector for compromise.

Links:

PostHeaderIcon SpringBatch: How to have different schedules, per environment, for instance: keep the fixedDelay=60000 in prod, but schedule with a Cron expression in local dev?

Case

In SpringBatch, a batch is scheduled in a bean JobScheduler with

[java]
@Scheduled(fixedDelay = 60000)
void doSomeThing(){…}
[/java]

.
How to have different schedules, per environment, for instance: keep the fixedDelay=60000 in prod, but schedule with a Cron expression in local dev?

Solution

Add this block to the <JobScheduler:

[java]
@Value("${jobScheduler.scheduling.enabled:true}")
private boolean schedulingEnabled;

@Value("${jobScheduler.scheduling.type:fixedDelay}")
private String scheduleType;

@Value("${jobScheduler.scheduling.fixedDelay:60000}")
private long fixedDelay;

@Value("${jobScheduler.scheduling.initialDelay:0}")
private long initialDelay;

@Value("${jobScheduler.scheduling.cron:}")
private String cronExpression;

@Scheduled(fixedDelayString = "${jobScheduler.scheduling.fixedDelay:60000}", initialDelayString = "${jobScheduler.scheduling.initialDelay:0}")
@ConditionalOnProperty(name = "jobScheduler.scheduling.type", havingValue = "fixedDelay")
public void scheduleFixedDelay() throws Exception {
if ("fixedDelay".equals(scheduleType) || "initialDelayFixedDelay".equals(scheduleType)) {
doSomething();
}
}

@Scheduled(cron = "${jobScheduler.scheduling.cron:0 0 1 * * ?}")
@ConditionalOnProperty(name = "jobScheduler.scheduling.type", havingValue = "cron", matchIfMissing = false)
public void scheduleCron() throws Exception {
if ("cron".equals(scheduleType)) {
doSomething(); }
}
[/java]

In application.yml, add:

[xml]
jobScheduler:
# noinspection GrazieInspection
scheduling:
enabled: true
type: fixedDelay
fixedDelay: 60000
initialDelay: 0
cron: 0 0 1 31 2 ? # every 31st of February… which means: never
[/xml]

(note the cron expression: leaving it empty may prevent SpringBoot from starting)

In application.yml, add:

[xml]
jobScheduler:
# noinspection GrazieInspection
scheduling:
type: cron
cron: 0 0 1 * * ?
[/xml]

It should work now ;-).

PostHeaderIcon SnowFlake❄: Why does SUM() return NULL instead of 0?

🙋‍♀️🙋‍♂️ Question

I’m running a SUM() query on a Snowflake table, but instead of returning 0 when there are no matching rows, it returns NULL.

For example, I have this table:

CREATE OR REPLACE TABLE sales (
    region STRING,
    amount NUMBER
);

INSERT INTO sales VALUES 
    ('North', 100),
    ('South', 200),
    ('North', NULL),
    ('East', 150);

Now, I run the following query to sum the sales for a region that doesn’t exist:

SELECT SUM(amount) FROM sales WHERE region = 'West';
  • Expected output: 0
  • Actual output: NULL

Why is this happening, and how can I make Snowflake return 0 instead of NULL?

Answer

 First and basic approach: explicitly filter out NULL before aggregation:
SELECT SUM(CASE WHEN amount IS NOT NULL THEN amount ELSE 0 END) AS total_sales 
FROM sales 
WHERE region = 'West'; 

This method ensures that NULL does not interfere with the SUM calculation.

✅ Even better: Use COALESCE() to handle NULL.

By default, SUM() returns NULL if there are no rows that match the condition or if all matching rows contain NULL.

🔹 To return 0 instead of NULL, use COALESCE(), which replaces NULL with a default value:

SELECT COALESCE(SUM(amount), 0) AS total_sales 
FROM sales 
WHERE region = 'West'; 

🔹 This ensures that when SUM(amount) is NULL, it gets converted to 0.

 (copied to https://stackoverflow.com/questions/79524739/why-does-sum-return-null-instead-of-0 )