Harness the power of GitHub Copilot. Learn more or get started now.
The post Game Bytes · March 2024 appeared first on The GitHub Blog.
]]>Game Bytes is our monthly series taking a peek at the world of gamedev on GitHub—featuring game engine updates, game jam details, open source games, mods, maps, and more. Game on!
After 11 years of development, KeeperRL is celebrating the big 1.0 and is now out of early access. Billed as “the ultimate evil wizard simulator,” KeeperRL is a roguelike base builder that lets you dig into the earth to expand your dungeon and build up to intimidate the countryside with a fortress. The 1.0 release introduces minor villains, new workshops, new items, and many other improvements. So, tent your fingers evilly and check out KeeperRL on Steam.
Canabalt has returned to the web! @AdamAtomic‘s 2009 Flash game didn’t invent the endless runner genre: it set it on fire. While it has remained available on various other platforms, it’s back in your browser with an official port by @ninjamuffin99. He tells us that it’s a very faithful port, since it was built with HaxeFlixel, a descendant of both Flash’s ActionScript and Canabalt‘s original Flixel framework. Start your daring escape now, or check out the source on GitHub.
The Unity team has released Megacity Metro, a demo for building a game in Unity with a little bit of everything: large scale multiplayer, cross-platform clients, prediction netcode, and server-authoritative gameplay. The open source demo is an interesting peek behind the curtain. If a game uses a lot of what Unity has to offer, what does it look like? Megacity Metro is one impressive answer. Head over to the project site or repo to learn more.
Classic first-person shooter Area 51 was originally released in 2005 for Playstation 2, Xbox, and Windows. The preservationists of Project Dreamland have since shared source code “found at a garage sale of a former THQ developer.” Work is now ongoing to see if the game can be built and run on modern systems. Get yourself ready to pay a visit to “Groom Lake” by checking out the project on GitHub.
Defold, the all-in-one cross-platform game engine and editor, has shipped version 1.7. Defold 1.7 includes a new API for converting world to local coordinates, getting and setting sprites’ vertex attributes, and many bug fixes. There’s much more to the release, but you’ll have to read the Defold 1.7 release notes to get all the details.
The Mirror is an all-in-one game development environment, built atop Godot Engine. The no/low-code engine and editor promises to help you “edit a game with friends in real-time.” The project just went open source on March 15. Read the announcement, then head over to the repo to get started.
Discord has unveiled a new embedded app SDK. Now in a preview, developers will be able to create multiplayer games and other social activities in an <iframe> that runs directly within the Discord client. The SDK handles coordination between Discord and the third-party applications. We’re excited to see how developers will connect chat communities with their games! Head over to the Discord site for more.
Phaser, a desktop and mobile web game framework, has shipped a brand new docs app called Phaser Explorer. Phaser Explorer is a new way to check out reference documentation, play with sample code, and explore the Phaser API. In a standout move for gamedev docs, Phaser Explorer is a progressive web application (PWA), so it works offline. Plus, in some browsers (such as Microsoft Edge), you can install the PWA as a standalone app. Try out Phaser Explorer now.
7DRL recently wrapped its 20th year of jamming to make and finish a roguelike in 7 days or less. Though voting is still in progress, it’s already clear that there are many great entries this year. Here are a few entries to play and hack on:
This month’s Game Jam Game of the Month may have you sliding dull rocks around, but the game itself is a gem. Lithic, an entry to Brackeys Game Jam 2024.1, is a sokoban puzzle game with a twist: to proceed, you’ll need some help from the statues that reside in Lithic‘s levels. The game won’t just test your logic skills, you’ll solve some lightly challenging word games, too. With excellent art, music, tutorialization, and written dialogue, don’t delay sliding into Lithic on itch.io.
The post Game Bytes · March 2024 appeared first on The GitHub Blog.
]]>The post Using GitHub Copilot in your IDE: Tips, tricks and best practices appeared first on The GitHub Blog.
]]>AI has become an integral part of my workflow these days, and with the assistance of GitHub Copilot, I move a lot faster when I’m building a project. Having used AI tools to increase my productivity over the past year, I’ve realized that similar to learning how to use a new framework or library, we can enhance our efficiency with AI tools by learning how to best use them.
In this blog post, I’ll share some of the daily things I do to get the most out of GitHub Copilot. I hope these tips will help you become a more efficient and productive user of the AI assistant.
Need a refresher on how to use GitHub Copilot?
Since GitHub Copilot continues to evolve in the IDE, CLI, and across GitHub.com, we put together a full guide on using GitHub Copilot with prompt tips and tricks. Get the guide > Want to learn how best to leverage it in the IDE? Keep on reading. ⤵ |
To make full use of the power of GitHub Copilot, it’s important to understand its capabilities. GitHub Copilot is developing rapidly, and new features are being added all the time. It’s no longer just a code completion tool in your editor—it now includes a chat interface that you can use in your IDE, a command line tool via a GitHub CLI extension, a summary tool in your pull requests, a helper tool in your terminals, and much, much more.
In a recent blog post, I’ve listed some of the ways you didn’t know you could use GitHub Copilot. This will give you a great overview of how much the AI assistant can currently do.
But beyond interacting with GitHub Copilot, how do you help it give you better answers? Well, the answer to that needs a bit more context.
If you understand Large Language Models ( LLMs), you will know that they are designed to make predictions based on the context provided. This means, the more contextually rich our input or prompt is, the better the prediction or output will be.
As such, learning to provide as much context as possible is key when interacting with GitHub Copilot, especially with the code completion feature. Unlike ChatGPT where you need to provide all the data to the model in the prompt window, by installing GitHub Copilot in your editor, the assistant is able to infer context from the code you’re working on. It then uses that context to provide code suggestions.
We already know this, but what else can we do to give it additional context?
I want to share a few essential tips with you to provide GitHub Copilot with more context in your editor to get the most relevant and useful code out of it:
Having your files open provides GitHub Copilot with context. When you have additional files open, it will help to inform the suggestion that is returned. Remember, if a file is closed, GitHub Copilot cannot see the file’s content in your editor, which means it cannot get the context from those closed files.
GitHub Copilot looks at the current open files in your editor to analyze the context, create a prompt that gets sent to the server, and return an appropriate suggestion.
Have a few files open in your editor to give GitHub Copilot a bigger picture of your project. You can also use #editor
in the chat interface to provide GitHub Copilot with additional context on your currently opened files in Visual Studio Code (VS Code) and Visual Studio.
Remember to close unneeded files when context switching or moving on to the next task.
Just as you would give a brief, high-level introduction to a coworker, a top-level comment in the file you’re working in can help GitHub Copilot understand the overall context of the pieces you will be creating—especially if you want your AI assistant to generate the boilerplate code for you to get going.
Be sure to include details about what you need and provide a good description so it has as much information as possible. This will help to guide GitHub Copilot to give better suggestions, and give it a goal on what to work on. Having examples, especially when processing data or manipulation strings, helps quite a bit.
It’s best to manually set the includes/imports or module references you need for your work, particularly if you’re working with a specific version of a package.
GitHub Copilot will make suggestions, but you know what dependencies you want to use. This can also help to let GitHub Copilot know what frameworks, libraries, and their versions you’d like it to use when crafting suggestions.
This can be helpful to jump start GitHub Copilot to a newer library version when it defaults to providing older code suggestions.
The name of your variables and functions matter. If you have a function named foo
or bar
, GitHub Copilot will not be able to give you the best completion because it isn’t able to infer intent from the names.
Just as the function name fetchData()
won’t mean much to a coworker (or you after a few months), fetchData()
won’t mean much to GitHub Copilot either.
Implementing good coding practices will help you get the most value from GitHub Copilot. While GitHub Copilot helps you code and iterate faster, remember the old rule of programming still applies: garbage in, garbage out.
Commenting your code helps you get very specific, targeted suggestions.
A function name can only be so descriptive without being overly long, so function comments can help fill in details that GitHub Copilot might need to know. One of the neat features about GitHub Copilot is that it can determine the correct comment syntax that is typically used in your programming language for function / method comments and will help create them for you based on what the code does. Adding more detail to these as the first change you do then helps GitHub Copilot determine what you would like to do in code and how to interact with that function.
Remember: Single, specific, short comments help GitHub Copilot provide better context.
Providing sample code to GitHub Copilot will help it determine what you’re looking for. This helps to ground the model and provide it with even more context.
It also helps GitHub Copilot generate suggestions that match the language and tasks you want to achieve, and return suggestions based on your current coding standards and practices. Unit tests provide one level of sample code at the individual function/method level, but you can also provide code examples in your project showing how to do things end to end. The cool thing about using GitHub Copilot long-term is that it nudges us to do a lot of the good coding practices we should’ve been doing all along.
Learn more about providing context to GitHub Copilot by watching this Youtube video:
Outside of providing enough context, there are some built-in features of GitHub Copilot that you may not be taking advantage of. Inline chat, for example, gives you an opportunity to almost chat with GitHub Copilot between your lines of code. By pressing CMD + I (CTRL + I on Windows) you’ll have Copilot right there to ask questions. This is a bit more convenient for quick fixes instead of opening up GitHub Copilot Chat’s side panel.
This experience provides you with code diffs inline, which is awesome. There are also special slash commands available like creating documentation with just the slash of a button!
GitHub Copilot Chat provides an experience in your editor where you can have a conversation with the AI assistant. You can improve this experience by using built-in features to make the most out of it.
For example, did you know that you can delete a previously asked question in the chat interface to remove it from the indexed conversation? Especially if it is no longer relevant?
Doing this will improve the flow of conversation and give GitHub Copilot only the necessary information needed to provide you with the best output.
Another tip I found is to use the up and down arrows to navigate through your conversation with GitHub Copilot Chat. I found myself scrolling through the chat interface to find that last question I asked, then discovered I can just use my keyboard arrows just like in the terminal!
@workspace
agentIf you’re using VS Code or Visual Studio, remember that agents are available to help you go even further. The @workspace
agent for example, is aware of your entire workspace and can answer questions related to it. As such, it can provide even more context when trying to get a good output from GitHub Copilot.
Another great tip when using GitHub Copilot Chat is to highlight relevant code in your files before asking it questions. This will help to give targeted suggestions and just provides the assistant with more context into what you need help with.
You can have multiple ongoing conversations with GitHub Copilot Chat on different topics by isolating your conversations with threads. We’ve provided a convenient way for you to start new conversations (thread) by clicking the + sign on the chat interface.
Slash commands are awesome, and there are quite a few of them. We have commands to help you explain code, fix code, create a new notebook, write tests, and many more. They are just shortcuts to common prompts that we’ve found to be particularly helpful in day-to-day development from our own internal usage.
Command | Description | Usage |
/explain | Get code explanations | Open file with code or highlight code you want explained and type:
|
/fix | Receive a proposed fix for the problems in the selected code | Highlight problematic code and type:
|
/tests | Generate unit tests for selected code | Open file with code or highlight code you want tests for and type:
|
/help | Get help on using Copilot Chat | Type:
|
/clear | Clear current conversation | Type:
|
/doc | Add a documentation comment | Highlight code and type:
You can also press CMD+I in your editor and type |
/generate | Generate code to answer your question | Type:
|
/optimize | Analyze and improve running time of the selected code | Highlight code and type:
|
/clear | Clear current chat | Type:
|
/new | Scaffold code for a new workspace | Type:
|
/simplify | Simplify the selected code | Highlight code and type:
|
/feedback | Provide feedback to the team | Type:
|
See the following image for commands available in VS Code:
In Visual Studio and VS Code, you can attach relevant files for GitHub Copilot Chat to reference by using #file
. This scopes GitHub Copilot to a particular context in your code base and provides you with a much better outcome.
To reference a file, type #
in the comment box, choose #file
and you will see a popup where you can choose your file. You can also type #file_name.py
in the comment box. See below for an example:
These days whenever I need to debug some code, I turn to GitHub Copilot Chat first. Most recently, I was implementing a decision tree and performed a k-fold cross-validation. I kept getting the incorrect accuracy scores and couldn’t figure out why. I turned to GitHub Copilot Chat for some assistance and it turns out I wasn’t using my training data set (X_train, y_train), even though I thought I was:
I'm catching up on my AI/ML studies today. I had to implement a DecisionTree and use the cross_val_score method to evaluate the model's accuracy score.
I couldn't figure out why the incorrect values for the accuracy scores were being returned, so I turned to Chat for some help pic.twitter.com/xn2ctMjAnr
— Kedasha is learning about AI + ML ✨ (@itsthatladydev) March 23, 2024
I figured this out a lot faster than I would’ve with external resources. I want to encourage you to start with GitHub Copilot Chat in your editor to get debugging help faster instead of going to external resources first. Follow my example above by explaining the problem, pasting the problematic code, and asking for help. You can also highlight the problematic code in your editor and use the /fix
command in the chat interface.
In VS Code, you can quickly get help from GitHub Copilot by looking out for “magic sparkles.” For example, in the commit comment section, clicking the magic sparkles will help you generate a commit message with the help of AI. You can also find magic sparkles inline in your editor as you’re working for a quick way to access GitHub Copilot inline chat.
Pressing them will use AI to help you fill out the data and more magic sparkles are being added where we find other places for GitHub Copilot to help in your day-to-day coding experience.
To get the best and most out of the tool, remember that context and prompt crafting is essential to keep in mind. Understanding where the tool shines best is also important. Some of the things GitHub Copilot is very good at include boilerplate code and scaffolding, writing unit tests, writing documentation, pattern matching, explaining uncommon or confusing syntax, cron jobs, and regex, and helping you remember things you’ve forgotten and debugging.
But never forget that you are in control, and GitHub Copilot is here as just that, your copilot. It is a tool that can help you write code faster, and it’s up to you to decide how to best use it.
It is not here to do your work for you or to write everything for you. It will guide you and nudge you in the right direction just as a coworker would if you asked them questions or for guidance on a particular issue.
I hope these tips and best practices were helpful. You can significantly improve your coding efficiency and output by properly leveraging GitHub Copilot. Learn more about how GitHub Copilot works by reading Inside GitHub: Working with the LLMs behind GitHub Copilot and Customizing and fine-tuning LLMs: What you need to know.
Harness the power of GitHub Copilot. Learn more or get started now.
The post Using GitHub Copilot in your IDE: Tips, tricks and best practices appeared first on The GitHub Blog.
]]>The post Insider newsletter digest: 4 things you didn’t know you could do with GitHub Projects appeared first on The GitHub Blog.
]]>This is abridged content from October 2023’s Insider newsletter. Like what you see? Sign up for the newsletter to receive complete, unabridged content in your inbox twice a month. Sign up now > |
Are you ready to unlock the secrets of organization, collaboration, and project magic? Buckle up, because we’ve got a handful of GitHub Projects tips and tricks that will turn you into a project management wizard! 🧙♂️💼 Keep reading for list of things you can do with GitHub Projects:
Some folks prefer to work in the terminal, and with the GitHub CLI project
command, you can manage and automate workflows from the command line. For example, you can create a new project board for your repository with a command like gh repo create-project
. Then, you can add issues to this board using the gh issue create
command, making it easy to manage and track your project’s progress from the command line.
If you often find yourself recreating projects with similar content and structure, you can set a project as a template when creating new projects. To set your project as a template, navigate to the project “Settings” page, and under the “Templates” section, toggle on Make template
. This will turn the project into a template that can be used with the green Use this template
button at the top of your project or when creating a new project.
If you’re an open source maintainer or a developer with multiple clients, you may be working across various organizations at a time. This also means you have multiple issues to keep track of, and GitHub Projects can help you collate issues from any organization onto a single project. You can do this in one of two ways:
Rather than spending time manually updating individual items, you can edit multiple items at once with the bulk editing feature. Let’s say you wanted to assign multiple issues to yourself. On the table layout, assign one issue, highlight and copy the contents of the cell, then select the remaining items you want to be assigned and paste the copied contents. And there you have it: you just assigned yourself to multiple issues at once. Check out this GIF for a visual representation:
Want even more tips and tricks? Check out this blog post for 10 more GitHub Projects tips, or learn how we use GitHub Projects to standardize our workflows and stay aligned. You’re now equipped to work your magic with GitHub Projects!
Want to receive content like this twice a month, right in your inbox? Sign up for the newsletter now >
The post Insider newsletter digest: 4 things you didn’t know you could do with GitHub Projects appeared first on The GitHub Blog.
]]>The post Found means fixed: Introducing code scanning autofix, powered by GitHub Copilot and CodeQL appeared first on The GitHub Blog.
]]>Starting today, code scanning autofix will be available in public beta for all GitHub Advanced Security customers. Powered by GitHub Copilot and CodeQL, code scanning autofix covers more than 90% of alert types in JavaScript, Typescript, Java, and Python, and delivers code suggestions shown to remediate more than two-thirds of found vulnerabilities with little or no editing.
Our vision for application security is an environment where found means fixed. By prioritizing the developer experience in GitHub Advanced Security, we already help teams remediate 7x faster than traditional security tools. Code scanning autofix is the next leap forward, helping developers dramatically reduce time and effort spent on remediation.
Even though applications remain a leading attack vector, most organizations admit to an ever-growing number of unremediated vulnerabilities that exist in production repositories. Code scanning autofix helps organizations slow the growth of this “application security debt” by making it easier for developers to fix vulnerabilities as they code.
Just as GitHub Copilot relieves developers of tedious and repetitive tasks, code scanning autofix will help development teams reclaim time formerly spent on remediation. Security teams will also benefit from a reduced volume of everyday vulnerabilities, so they can focus on strategies to protect the business while keeping up with an accelerated pace of development.
Want to try code scanning autofix? If your organization is new to GitHub or does not yet have GitHub Advanced Security (or, its prerequisite, GitHub Enterprise), contact us to request a demo and set up a free trial.
When a vulnerability is discovered in a supported language, fix suggestions will include a natural language explanation of the suggested fix, together with a preview of the code suggestion that the developer can accept, edit, or dismiss. In addition to changes to the current file, these code suggestions can include changes to multiple files and the dependencies that should be added to the project.
Want to learn more about how we do it? Read Fixing security vulnerabilities with AI: A peek under the hood of code scanning autofix.
Behind the scenes, code scanning autofix leverages the CodeQL engine and a combination of heuristics and GitHub Copilot APIs to generate code suggestions. To learn more about autofix and its data sources, capabilities, and limitations, please see About autofix for CodeQL code scanning.
We’ll continue to add support for more languages, with C# and Go coming next. We also encourage you to join the autofix feedback and resources discussion to share your experiences and help guide further improvements to the autofix experience. Together, we can help move application security closer to a place where a vulnerability found means a vulnerability fixed.
To help you learn more, GitHub has published extensive resources and documentation about the system architecture, data flow, and AI policies governing code scanning autofix.
If you want to give code scanning autofix a try, but your organization is new to GitHub or does not yet have GitHub Advanced Security (or, its prerequisite, GitHub Enterprise), contact us to request a demo and set up a free trial.
The post Found means fixed: Introducing code scanning autofix, powered by GitHub Copilot and CodeQL appeared first on The GitHub Blog.
]]>The post Gaining kernel code execution on an MTE-enabled Pixel 8 appeared first on The GitHub Blog.
]]>MTE is a very well documented feature on newer Arm processors that uses hardware implementations to check for memory corruption. As there are already many good articles about MTE, I’ll only briefly go through the idea of MTE and explain its significance in comparison to other mitigations for memory corruption. Readers who are interested in more details can, for example, consult this article and the whitepaper released by Arm.
While the Arm64 architecture uses 64 bit pointers to access memory, there is usually no need to use such a large address space. In practice, most applications use a much smaller address space (usually 52 bits or less). This leaves the highest bits in a pointer unused. The main idea of memory tagging is to use these higher bits in an address to store a “tag” that can then be used to check against the other tag stored in the memory block associated with the address. The helps to mitigate common types of memory corruptions as follows:
In the case of a linear overflow, a pointer is used to dereference an adjacent memory block that has a different tag compared to the one stored in the pointer. By checking these tags at dereference time, the corrupted dereference can be detected. For use-after-free type memory corruptions, as long as the tag in a memory block is cleared every time it is freed and a new tag reassigned when it is allocated, dereferencing an already freed and reclaimed object will also lead to a discrepancy between pointer tag and the tag in memory, which allows use-after-free to be detected.
(The above image is from Memory Tagging Extension: Enhancing memory safety through architecture published by Arm.)
The main reason that memory tagging is different from previous mitigations, such as Kernel Control Flow Integrity (kCFI) is that, unlike other mitigations, which disrupts later stages of an exploit, MTE is a very early stage mitigation that tries to catch memory corruption when it first happens. As such, it is able to stop an exploit in a very early stage before the attacker has gained any capabilities and it is therefore very difficult to bypass. It introduces checks that effectively turns an unsafe memory language into one that is memory safe, albeit probabilistically.
In theory, memory tagging can be implemented in software alone, by making the memory allocator assign and remove tags everytime memory is allocated or free, and by adding tag checking logic when dereferencing pointers. Doing so, however, incurs a performance cost that makes it unsuitable for production use. As a result, hardware implementation is needed to reduce the performance cost and to make memory tagging viable for production use. The hardware support was introduced in the v8.5a version of the ARM architecture, in which extra hardware instructions (called MTE) were introduced to perform tagging and checking. For Android devices, most chipsets that support MTE use Arm v9 processors (instead of Arm v8.5a), and currently there are only a handful of devices that support MTE.
One of the limitations of MTE is that the number of available unused bits is small compared to all possible memory blocks that can ever be allocated. As such, tag collision is inevitable and many memory blocks will have the same tag. This means that a corrupted memory access may still succeed by chance. In practice, even when using only 4 bits for the tag, the success rate is reduced to 1/16, which is still a fairly strong protection against memory corruption. Another limitation is that, by leaking pointer and memory block values using side channel attack such as Spectre, an attacker may be able to ensure that a corrupted memory access is done with the correct tag and thus bypasses MTE. This type of leak, however, is mostly only available to a local attacker. The series of articles, MTE As Implemented by Mark Brand, includes an in-depth study of the limitations and impact of MTE on various attack scenarios.
Apart from having hardware that uses processors that implements Arm v8.5a or above, software support is also required to enable MTE. Currently, only Google’s Pixel 8 allows users to enable MTE in the developer options and MTE is disabled by default. Extra steps are also required to enable MTE in the kernel.
The Arm Mali GPU can be integrated in various devices, (for example, see “Implementations” in Mali (GPU) Wikipedia entry). It has been an attractive target on Android phones and has been targeted by in-the-wild exploits multiple times. The current vulnerability is closely related to another issue that I reported and is a vulnerability in the handling of a type of GPU memory called JIT memory. I’ll now briefly explain JIT memory and explain the vulnerability CVE-2023-6241.
When using the Mali GPU driver, a user app first needs to create and initialize a kbase_context
kernel object. This involves the user app opening the driver file and using the resulting file descriptor to make a series of ioctl
calls. A kbase_context
object is responsible for managing resources for each driver file that is opened and is unique for each file handle.
In particular, the kbase_context
manages different types of memory that are shared between the GPU device and user space applications. User applications can either map their own memory to the memory space of the GPU so the GPU can access this memory, or they can allocate memory from the GPU. Memory allocated by the GPU is managed by the kbase_context
and can be mapped to the GPU memory space and also mapped to user space. A user application can also use the GPU to access mapped memory by submitting commands to the GPU. In general, memory needs to be either allocated and managed by the GPU (native memory) or imported to the GPU from user space, and then mapped to the GPU address space before it can be accessed by the GPU. A memory region in the Mali GPU is represented by the kbase_va_region
. Similar to virtual memory in the CPU, a memory region in the GPU may not have its entire range backed by physical memory. The nr_pages
field in a kbase_va_region
specifies the virtual size of the memory region, whereas gpu_alloc->nents
is the actual number of physical pages that are backing the region. I’ll refer to these pages as the backing pages of the region from now on. While the virtual size of a memory region is fixed, its physical size can change. From now on, when I use terminologies such as resize, grow and shrink regarding a memory region, what I mean is that the physical size of the region is resizing, growing or shrinking.
The JIT memory is a type of native memory whose lifetime is managed by the kernel driver. User applications request the GPU to allocate and free JIT memory by sending relevant commands to the GPU. While most commands, such as those using GPU to perform arithmetic and memory accesses are executed on the GPU itself, there are some commands, such as the ones used for managing JIT memory, that are implemented in the kernel and executed on the CPU. These are called software commands (in contrast to hardware commands that are executed on the GPU (hardware)). On GPUs that use the Command Stream Frontend (CSF), software commands and hardware commands are placed on different types of command queues. To submit a software command, a kbase_kcpu_command_queue
is needed and it can be created by using the KBASE_IOCTL_KCPU_QUEUE_CREATE
ioctl
. A software command can then be queued using the KBASE_IOCTL_KCPU_QUEUE_ENQUEUE
command. To allocate or free JIT memory, commands of type BASE_KCPU_COMMAND_TYPE_JIT_ALLOC
and BASE_KCPU_COMMAND_TYPE_JIT_FREE
can be used.
The BASE_KCPU_COMMAND_TYPE_JIT_ALLOC
command uses kbase_jit_allocate
to allocate JIT memory. Similarly, the command BASE_KCPU_COMMAND_TYPE_JIT_FREE
can be used to free JIT memory. As explained in the section “The life cycle of JIT memory” in one of my previous posts, when JIT memory is freed, it goes into a memory pool managed by the kbase_context
and when kbase_jit_allocate
is called, it first looks into this memory pool to see if there is any suitable freed JIT memory that can be reused:
struct kbase_va_region *kbase_jit_allocate(struct kbase_context *kctx,
const struct base_jit_alloc_info *info,
bool ignore_pressure_limit)
{
...
kbase_gpu_vm_lock(kctx);
mutex_lock(&kctx->jit_evict_lock);
/*
* Scan the pool for an existing allocation which meets our
* requirements and remove it.
*/
if (info->usage_id != 0)
/* First scan for an allocation with the same usage ID */
reg = find_reasonable_region(info, &kctx->jit_pool_head, false);
...
}
If an existing region is found and its virtual size matches the request, but its physical size is too small, then kbase_jit_allocate
will attempt to allocate more physical pages to back the region by calling kbase_jit_grow
:
struct kbase_va_region *kbase_jit_allocate(struct kbase_context *kctx,
const struct base_jit_alloc_info *info,
bool ignore_pressure_limit)
{
...
/* kbase_jit_grow() can release & reacquire 'kctx->reg_lock',
* so any state protected by that lock might need to be
* re-evaluated if more code is added here in future.
*/
ret = kbase_jit_grow(kctx, info, reg, prealloc_sas,
mmu_sync_info);
...
}
If, on the other hand, no suitable region is found, kbase_jit_allocate
will allocate JIT memory from scratch:
struct kbase_va_region *kbase_jit_allocate(struct kbase_context *kctx,
const struct base_jit_alloc_info *info,
bool ignore_pressure_limit)
{
...
} else {
/* No suitable JIT allocation was found so create a new one */
u64 flags = BASE_MEM_PROT_CPU_RD | BASE_MEM_PROT_GPU_RD |
BASE_MEM_PROT_GPU_WR | BASE_MEM_GROW_ON_GPF |
BASE_MEM_COHERENT_LOCAL |
BASEP_MEM_NO_USER_FREE;
u64 gpu_addr;
...
mutex_unlock(&kctx->jit_evict_lock);
kbase_gpu_vm_unlock(kctx);
reg = kbase_mem_alloc(kctx, info->va_pages, info->commit_pages, info->extension,
&flags, &gpu_addr, mmu_sync_info);
...
}
As we can see from the comment above the call to kbase_jit_grow
, kbase_jit_grow
can temporarily drop the kctx->reg_lock
:
static int kbase_jit_grow(struct kbase_context *kctx,
const struct base_jit_alloc_info *info,
struct kbase_va_region *reg,
struct kbase_sub_alloc **prealloc_sas,
enum kbase_caller_mmu_sync_info mmu_sync_info)
{
...
if (!kbase_mem_evictable_unmake(reg->gpu_alloc))
goto update_failed;
...
old_size = reg->gpu_alloc->nents; //commit_pages - reg->gpu_alloc->nents; //<---------2.
pages_required = delta;
...
while (kbase_mem_pool_size(pool) mem_partials_lock);
kbase_gpu_vm_unlock(kctx); //<---------- lock dropped.
ret = kbase_mem_pool_grow(pool, pool_delta);
kbase_gpu_vm_lock(kctx);
...
}
In the above, we see that kbase_gpu_vm_unlock
is called to temporarily drop the kctx->reg_lock
, while kctx->mem_partials_lock
is also dropped during a call to kbase_mem_pool_grow
. In the Mali GPU, the kctx->reg_lock
is used for protecting concurrent accesses to memory regions. So, for example, when kctx->reg_lock
is held, the physical size of the memory region cannot be changed by another thread. In GHSL-2023-005 that I reported previously, I was able to trigger a race so that the JIT region was shrunk by using the KBASE_IOCTL_MEM_COMMIT
ioctl
from another thread while kbase_mem_pool_grow
was running. This change in the size of the JIT region caused reg->gpu_alloc->nents
to change after kbase_mem_pool_grow
, meaning that the actual value of reg->gpu_alloc->nents
was then different from the value that was cached in old_size
and delta
(1. and 2. in the above). As these values were later used to allocate and map the JIT region, using these stale values caused inconsistency in the GPU memory mapping, causing GHSL-2023-005.
static int kbase_jit_grow(struct kbase_context *kctx,
const struct base_jit_alloc_info *info,
struct kbase_va_region *reg,
struct kbase_sub_alloc **prealloc_sas,
enum kbase_caller_mmu_sync_info mmu_sync_info)
{
...
//grow memory pool
...
//delta use for allocating pages
gpu_pages = kbase_alloc_phy_pages_helper_locked(reg->gpu_alloc, pool,
delta, &prealloc_sas[0]);
...
//old_size used for growing gpu mapping
ret = kbase_mem_grow_gpu_mapping(kctx, reg, info->commit_pages,
old_size);
...
}
After GHSL-2023-005 was patched, it was no longer possible to change the size of JIT memory using the KBASE_IOCTL_MEM_COMMIT ioctl
.
Similar to virtual memory, when an address in a memory region that is not backed by a physical page is accessed by the GPU, a memory access fault happens. In this case, depending on the type of the memory region, it may be possible to allocate and map a physical page on the fly to back the fault address. A GPU memory access fault is handled by the kbase_mmu_page_fault_worker
:
void kbase_mmu_page_fault_worker(struct work_struct *data)
{
...
kbase_gpu_vm_lock(kctx);
...
if ((region->flags & GROWABLE_FLAGS_REQUIRED)
!= GROWABLE_FLAGS_REQUIRED) {
kbase_gpu_vm_unlock(kctx);
kbase_mmu_report_fault_and_kill(kctx, faulting_as,
"Memory is not growable", fault);
goto fault_done;
}
if ((region->flags & KBASE_REG_DONT_NEED)) {
kbase_gpu_vm_unlock(kctx);
kbase_mmu_report_fault_and_kill(kctx, faulting_as,
"Don't need memory can't be grown", fault);
goto fault_done;
}
...
spin_lock(&kctx->mem_partials_lock);
grown = page_fault_try_alloc(kctx, region, new_pages, &pages_to_grow,
&grow_2mb_pool, prealloc_sas);
spin_unlock(&kctx->mem_partials_lock);
...
}
Within the fault handler, a number of checks are performed to ensure that the memory region is allowed to grow in size. The two checks that are relevant to JIT memory are the checks for the GROWABLE_FLAGS_REQUIRED
and the KBASE_REG_DONT_NEED
flags. The GROWABLE_FLAGS_REQUIRED
is defined as follows:
#define GROWABLE_FLAGS_REQUIRED (KBASE_REG_PF_GROW | KBASE_REG_GPU_WR)
These flags are added to a JIT region when it is created by kbase_jit_allocate
and are never changed:
struct kbase_va_region *kbase_jit_allocate(struct kbase_context *kctx,
const struct base_jit_alloc_info *info,
bool ignore_pressure_limit)
{
...
} else {
/* No suitable JIT allocation was found so create a new one */
u64 flags = BASE_MEM_PROT_CPU_RD | BASE_MEM_PROT_GPU_RD |
BASE_MEM_PROT_GPU_WR | BASE_MEM_GROW_ON_GPF | //jit_evict_lock);
kbase_gpu_vm_unlock(kctx);
reg = kbase_mem_alloc(kctx, info->va_pages, info->commit_pages, info->extension,
&flags, &gpu_addr, mmu_sync_info);
...
}
While the KBASE_REG_DONT_NEED
flag is added to a JIT region when it is freed, it is removed in kbase_jit_grow
well before the kctx->reg_lock
and kctx->mem_partials_lock
are dropped and kbase_mem_pool_grow
is called:
static int kbase_jit_grow(struct kbase_context *kctx,
const struct base_jit_alloc_info *info,
struct kbase_va_region *reg,
struct kbase_sub_alloc **prealloc_sas,
enum kbase_caller_mmu_sync_info mmu_sync_info)
{
...
if (!kbase_mem_evictable_unmake(reg->gpu_alloc)) //<----- Remove KBASE_REG_DONT_NEED
goto update_failed;
...
while (kbase_mem_pool_size(pool) mem_partials_lock);
kbase_gpu_vm_unlock(kctx);
ret = kbase_mem_pool_grow(pool, pool_delta); //<----- race window: fault handler grows region
kbase_gpu_vm_lock(kctx);
...
}
In particular, during the race window marked in the above snippet, the JIT memory reg is allowed to grow when a page fault happens.
So, by accessing unmapped memory in the region to create a fault on another thread while kbase_mem_pool_grow
is running, I can cause the JIT region to be grown by the GPU fault handler while kbase_mem_pool_grow
runs. This then changes reg->gpu_alloc->nents
and invalidates old_size
and delta
in 1. and 2. below:
static int kbase_jit_grow(struct kbase_context *kctx,
const struct base_jit_alloc_info *info,
struct kbase_va_region *reg,
struct kbase_sub_alloc **prealloc_sas,
enum kbase_caller_mmu_sync_info mmu_sync_info)
{
...
if (!kbase_mem_evictable_unmake(reg->gpu_alloc))
goto update_failed;
...
old_size = reg->gpu_alloc->nents; //commit_pages - reg->gpu_alloc->nents; //<---------2.
pages_required = delta;
...
while (kbase_mem_pool_size(pool) mem_partials_lock);
kbase_gpu_vm_unlock(kctx);
ret = kbase_mem_pool_grow(pool, pool_delta); //gpu_alloc->nents changed by fault handler
kbase_gpu_vm_lock(kctx);
...
//delta use for allocating pages
gpu_pages = kbase_alloc_phy_pages_helper_locked(reg->gpu_alloc, pool, //commit_pages, //<----- 4.
old_size);
...
}
As a result, when delta
and old_size
are used in 3. and 4. to allocate backing pages and to map the pages to the GPU memory space, their values are invalid.
This is very similar to what happened with GHSL-2023-005. As kbase_mem_pool_grow
involves large memory allocations, this race can be won very easily. There is, however, one very big difference here: With GHSL-2023-005, I was able to shrink the JIT region while in this case, I was only able to grow the JIT region. To understand why this matters, let’s have a brief recap of how my exploit for GHSL-2023-005 worked.
As mentioned before, the physical size, or the number of backing pages of a kbase_va_region
is stored in the field reg->gpu_alloc->nents
. A kbase_va_region
has two kbase_mem_phy_alloc
objects: the cpu_alloc
and gpu_alloc
that are responsible for managing its backing pages. For Android devices, these two fields are configured to be the same. Within kbase_mem_phy_alloc
, the field pages
is an array that contains the physical addresses of the backing pages, while nents
specifies the length of the pages
array:
struct kbase_mem_phy_alloc {
...
size_t nents;
struct tagged_addr *pages;
...
}
When kbase_alloc_phy_pages_helper_locked
is called, it allocates memory pages and appends the physical addresses represented by these pages to the array pages
, so the new pages are added to the index nents
onwards. The new size is then stored to nents
. For example, when it is called in kbase_jit_grow
, delta
is the number of pages to add:
static int kbase_jit_grow(struct kbase_context *kctx,
const struct base_jit_alloc_info *info,
struct kbase_va_region *reg,
struct kbase_sub_alloc **prealloc_sas,
enum kbase_caller_mmu_sync_info mmu_sync_info)
{
...
//delta use for allocating pages
gpu_pages = kbase_alloc_phy_pages_helper_locked(reg->gpu_alloc, pool,
delta, &prealloc_sas[0]);
...
}
In this case, delta
pages are inserted at the index nents
in the array pages
of gpu_alloc
:
After the backing pages are allocated and inserted into the pages
array, the new pages are mapped to the GPU address space by calling kbase_mem_grow_gpu_mapping
. The virtual address of a kbase_va_region
in the GPU memory space is managed by the kbase_va_region
itself and is stored in the fields start_pfn
and nr_pages
:
struct kbase_va_region {
...
u64 start_pfn;
...
size_t nr_pages;
...
}
The start of the virtual address of a kbase_va_region
is stored in start_pfn
(as a page frame, so the actual address is start_pfn >> PAGE_SIZE
) while nr_pages
stores the size of the region. These fields remain unchanged after they are set. Within a kbase_va_region
, the initial reg->gpu_alloc->nents
pages in the virtual address space are backed by the physical memory stored in the pages
array of gpu_alloc->pages
, while the rest of the addresses are not backed. In particular, the virtual addresses that are backed are always contiguous (so, no gaps between backed regions) and always start from the start of the region. For example, the following is possible:
While the following case is not allowed because the backing does not start from the beginning of the region:
and this following case is also not allowed because of the gaps in the addresses that are backed:
In the case when kbase_mem_grow_gpu_mapping
is called in kbase_jit_grow
, the GPU addresses between (start_pfn + old_size) * 0x1000
to (start_pfn + info->commit_pages) * 0x1000
are mapped to the newly added pages in gpu_alloc->pages
, which are the pages between indices pages + old_size
and pages + info->commit_pages
(because delta = info->commit_pages - old_size
):
static int kbase_jit_grow(struct kbase_context *kctx,
const struct base_jit_alloc_info *info,
struct kbase_va_region *reg,
struct kbase_sub_alloc **prealloc_sas,
enum kbase_caller_mmu_sync_info mmu_sync_info)
{
...
old_size = reg->gpu_alloc->nents;
delta = info->commit_pages - reg->gpu_alloc->nents;
...
//old_size used for growing gpu mapping
ret = kbase_mem_grow_gpu_mapping(kctx, reg, info->commit_pages,
old_size);
...
}
In particular, old_size
here is used to specify both the GPU address where the new mapping should start, and also the offset from the pages
array where backing pages should be used.
If reg->gpu_alloc->nents
changes after old_size
and delta
are cached, then these offsets may become invalid. For example, if the kbase_va_region
was shrunk and nents
decreased after old_size
and delta
were stored, then kbase_alloc_phy_pages_helper_locked
will insert delta
pages to reg->gpu_alloc->pages + nents
:
Similarly, kbase_mem_grow_gpu_mapping
will map the GPU addresses starting from (start_pfn + old_size) * 0x1000
, using the pages that are between reg->gpu_alloc->pages + old_size
and reg->gpu_alloc->pages + nents + delta
(dotted lines in the figure below). This means that the pages between pages->nents
and pages->old_size
don’t end up getting mapped to any GPU addresses, while some addresses end up having no backing pages:
GHSL-2023-005 enabled me to shrink the JIT region but CVE-2023-6241 does not give me that capability. To understand how to exploit this issue, we need to know a bit more about how GPU mappings are removed. The function kbase_mmu_teardown_pgd_pages
is responsible for removing address mappings from the GPU. This function essentially walks through a GPU address range and removes the addresses from the GPU page table by marking them as invalid. If it encounters a high level page table entry (PTE), which covers a large range of addresses, and finds that the entry is invalid, then it’ll skip removing the entire range of addresses covered by the entry. For example, a level 2 page table entry covers a range of 512 pages, so if a level 2 page table entry is found to be invalid (1. in the below), then kbase_mmu_teardown_pgd_pages
will assume the next 512 pages are covered by this level 2 and hence are all invalid already. As such, it’ll skip removing these pages (2. in the below).
static int kbase_mmu_teardown_pgd_pages(struct kbase_device *kbdev, struct kbase_mmu_table *mmut,
u64 vpfn, size_t nr, u64 *dirty_pgds,
struct list_head *free_pgds_list,
enum kbase_mmu_op_type flush_op)
{
...
for (level = MIDGARD_MMU_TOPLEVEL;
level ate_is_valid(page[index], level))
break; /* keep the mapping */
else if (!mmu_mode->pte_is_valid(page[index], level)) { //<------ 1.
/* nothing here, advance */
switch (level) {
...
case MIDGARD_MMU_LEVEL(2):
count = 512; // nr)
count = nr;
goto next;
}
...
next:
kunmap(phys_to_page(pgd));
vpfn += count;
nr -= count;
The function kbase_mmu_teardown_pgd_pages
is called either when a kbase_va_region
is shrunk or when it is deleted. As explained in the previous section, the virtual addresses in a kbase_va_region
that are mapped and backed by physical pages must be contiguous from the start of the kbase_va_region
. As a result, if any address in the region is mapped, then the start address must be mapped and hence the high level page table entry covering the start address must be valid (if no address in the region is mapped, then kbase_mmu_teardown_pgd_pages
would not even be called):
In the above, the level 2 PTE that covers the start address of the region is mapped and so it is valid, therefore in this case, if kbase_mmu_teardown_pgd_pages
ever encounters an unmapped high level PTE, the rest of the addresses in the kbase_va_region
must have already been unmapped and can be skipped safely.
In the case where a region is shrunk, the address where the unmapping starts lies within the kbase_va_region
, and the entire range between this start address and the end of the region will be unmapped. If the level 2 page table entry covering this address is invalid, then the start address must be in a region that is not mapped, and hence the rest of the address range to unmap must also not have been mapped. In this case, skipping of addresses is again safe:
So, as long as regions are only mapped from their start addresses and have no gaps in the mappings, kbase_mmu_teardown_pgd_pages
will behave correctly.
In the case of GHSL-2023-005, it is possible to create a region that does not meet these conditions. For example, by shrinking the entire region to size zero during the race window, it is possible to create a region where the start of the region is unmapped:
When the region is deleted, and kbase_mmu_teardown_pgd_pages
tries to remove the first address, because the level 2 PTE is invalid, it’ll skip the next 512 pages, some of which may actually have been mapped:
In this case, addresses in the “incorrectly skipped” region will remain mapped to some entries in the pages
array in the gpu_alloc
, which are already freed. And these “incorrectly skipped” GPU addresses can be used to access already freed memory pages.
The situation, however, is very different when a region is grown during the race window. In this case, nents
is larger than old_size
when kbase_alloc_phy_pages_helper_locked
and kbase_mem_grow_gpu_mapping
are called, and delta
pages are being inserted at index nents
of the pages
array:
The pages
array contains the correct number of pages to backup both the jit grow and the fault access, and is in fact exactly how it should be when kbase_jit_grow
is called after the page fault handler.
When kbase_mem_grow_gpu_mapping
is called, delta
pages are mapped to the GPU from (start_pfn + old_size) * 0x1000
. As the total number of backing pages has now increased by fh + delta
, where fh
is the number of pages added by the fault handler, this leaves the last fh
pages in the pages
array unmapped.
This, however, does not seem to create any problem either. The memory region still only has its start addresses mapped and there is no gap in the mapping. The pages that are not mapped are simply not accessible from the GPU and will get freed when the memory region is deleted, so it isn’t even a memory leak issue.
However, not all is lost. As we have seen, when a GPU page fault happens, if the cause of the fault is that the address is not mapped, then the fault handler will try to add backing pages to the region and map these new pages to the extent of the region. If the fault address is, say fault_addr
, then the minimum number of pages to add is new_pages = fault_addr/0x1000 - reg->gpu_alloc->nents
. Depending on the kbase_va_region
, some padding may also be added. In any case, these new pages will be mapped to the GPU, starting from the address (start_pfn + reg->gpu_alloc->nents) * 0x1000
, so as to preserve the fact that only the addresses at the start of a region are mapped.
This means that, if I trigger another GPU fault in the JIT region that was affected by the bug, then some new mappings will be added after the region that is not mapped.
This creates a gap in the GPU mappings, and I’m starting to get something that looks exploitable.
Note that as delta
has to be non zero to trigger the bug, and as delta + old_size
pages at the start of the region are mapped, it is still not possible to have the start of the region unmapped like in the case of GHSL-2023-005. So, my only option here is to shrink the region and have the resulting size lie somewhere inside the unmapped gap.
The only way to shrink a JIT region is to use the BASE_KCPU_COMMAND_TYPE_JIT_FREE
GPU command to “free” the JIT region. As explained before, this does not actually free the kbase_va_region
itself, but rather puts it in a memory pool so that it may be reused on subsequent JIT allocation. Prior to this, kbase_jit_free
will also shrink the JIT region according to the initial_commit
size of the region, as well as the trim_level
that is configured in the kbase_context
:
void kbase_jit_free(struct kbase_context *kctx, struct kbase_va_region *reg)
{
...
old_pages = kbase_reg_current_backed_size(reg);
if (reg->initial_commit initial_commit,
div_u64(old_pages * (100 - kctx->trim_level), 100));
u64 delta = old_pages - new_size;
if (delta) {
mutex_lock(&kctx->reg_lock);
kbase_mem_shrink(kctx, reg, old_pages - delta);
mutex_unlock(&kctx->reg_lock);
}
}
...
}
Either way, I can control the size of this shrinking. With this in mind, I can arrange the region in the following way:
fault_size
pages, enough pages to cover at least one level 2 PTE.
After the bug is triggered, only the initial old_size + delta
pages are mapped to the GPU address space, while the kbase_va_region
has old_size + delta + fault_size
backing pages in total.
Trigger a second fault at an offset greater than the number of backing pages, so that pages are appended to the region and mapped after the unmapped regions created in the previous step.
Free the JIT region using BASE_KCPU_COMMAND_TYPE_JIT_FREE
, which will call kbase_jit_free
to shrink the region and remove pages from it. Control the size of this trimming either so that the region size after shrinking (final_size
) of the backing store lies somewhere within the unmapped region covered by the first level 2 PTE.
When the region is shrunk, kbase_mmu_teardown_pgd_pages
is called to unmap the GPU address mappings, starting from region_start + final_size
all the way up to the end of the region. As the entire address range covered by the first level 2 PTE is unmapped, when kbase_mmu_teardown_pgd_pages
tries to unmap region_start + final_size
, the condition !mmu_mode->pte_is_valid
is met at a level 2 PTE and so the unmapping will skip the next 512 pages, starting from region_start + final_size
. However, since addresses belonging to the next level 2 PTE are still mapped, these addresses will be skipped incorrectly (the orange region in the next figure), leaving them mapped to pages that are going to be freed:
Once the shrinking is completed, the backing pages are freed and the addresses in the orange region will retain access to already freed pages.
This means that the freed backing page can now be reused as any kernel page, which gives me plenty of options to exploit this bug. One possibility is to use my previous technique to replace the backing page as page table global directories (PGD) of our GPU kbase_context
.
To recap, let’s take a look at how the backing pages of a kbase_va_region
are allocated. When allocating pages for the backing store of a kbase_va_region
, the kbase_mem_pool_alloc_pages
function is used:
int kbase_mem_pool_alloc_pages(struct kbase_mem_pool *pool, size_t nr_4k_pages,
struct tagged_addr *pages, bool partial_allowed)
{
...
/* Get pages from this pool */
while (nr_from_pool--) {
p = kbase_mem_pool_remove_locked(pool); //next_pool) {
/* Allocate via next pool */
err = kbase_mem_pool_alloc_pages(pool->next_pool, //<----- 2.
nr_4k_pages - i, pages + i, partial_allowed);
...
} else {
/* Get any remaining pages from kernel */
while (i != nr_4k_pages) {
p = kbase_mem_alloc_page(pool); //<------- 3.
...
}
...
}
...
}
The input argument kbase_mem_pool
is a memory pool managed by the kbase_context
object associated with the driver file that is used to allocate the GPU memory. As the comments suggest, the allocation is actually done in tiers. First the pages will be allocated from the current kbase_mem_pool
using kbase_mem_pool_remove_locked
(1 in the above). If there is not enough capacity in the current kbase_mem_pool
to meet the request, then pool->next_pool
, is used to allocate the pages (2 in the above). If even pool->next_pool
does not have the capacity, then kbase_mem_alloc_page
is used to allocate pages directly from the kernel via the buddy allocator (the page allocator in the kernel).
When freeing a page, provided that the memory region is not evicted, the same happens in the opposite direction: kbase_mem_pool_free_pages
first tries to return the pages to the kbase_mem_pool
of the current kbase_context
, if the memory pool is full, it’ll try to return the remaining pages to pool->next_pool
. If the next pool is also full, then the remaining pages are returned to the kernel by freeing them via the buddy allocator.
As noted in my post Corrupting memory without memory corruption, pool->next_pool
is a memory pool managed by the Mali driver and shared by all the kbase_context
. It is also used for allocating page table global directories (PGD) used by GPU contexts. In particular, this means that by carefully arranging the memory pools, it is possible to cause a freed backing page in a kbase_va_region
to be reused as a PGD of a GPU context. (The details of how to achieve this can be found here.)
Once the freed page is reused as a PGD of a GPU context, the GPU addresses that retain access to the freed page can be used to rewrite the PGD from the GPU. This then allows any kernel memory, including kernel code, to be mapped to the GPU. This then allows me to rewrite kernel code and hence execute arbitrary kernel code. It also allows me to read and write arbitrary kernel data, so I can easily rewrite credentials of my process to gain root, as well as to disable SELinux.
The exploit for Pixel 8 can be found here with some setup notes.
So far, I’ve not mentioned any specific measures to bypass MTE. In fact, MTE does not affect the exploit flow of this bug at all. While MTE protects against dereferences of pointers against inconsistent memory blocks, the exploit does not rely on any of such dereferencing at all. When the bug is triggered, it creates inconsistencies between the pages
array and the GPU mappings of the JIT region. At this point, there is no memory corruption and neither the GPU mappings nor the pages
array, when considered separately, contain invalid entries. When the bug is used to cause kbase_mmu_teardown_pgd_pages
to skip removing GPU mappings, its effect is to cause physical addresses of freed memory pages to be retained in the GPU page table. So, when the GPU accesses the freed pages, it is in fact accessing their physical addresses directly, which does not involve any pointer dereferencing either. On top of that, I’m also not sure whether MTE has any effect on GPU memory accesses anyway. So, by using the GPU to access physical addresses directly, I’m able to completely bypass the protection that MTE offers. Ultimately, there is no memory safe code in the code that manages memory accesses. At some point, physical addresses will have to be used directly to access memory.
In this post, I’ve shown how CVE-2023-6241 can be used to gain arbitrary kernel code execution on a Pixel 8 with kernel MTE enabled. While MTE is arguably one of the most significant advances in the mitigations against memory corruptions and will render many memory corruption vulnerabilities unexploitable, it is not a silver bullet and it is still possible to gain arbitrary kernel code execution with a single bug. The bug in this post bypasses MTE by using a coprocessor (GPU) to access physical memory directly (Case 4 in MTE As Implemented, Part 3: The Kernel). With more and more hardware and software mitigations implemented on the CPU side, I expect coprocessors and their kernel drivers to continue to be a powerful attack surface.
The post Gaining kernel code execution on an MTE-enabled Pixel 8 appeared first on The GitHub Blog.
]]>The post GitHub Availability Report: February 2024 appeared first on The GitHub Blog.
]]>February 26 18:34 UTC (lasting 53 minutes)
February 29 09:32 UTC (lasting 142 minutes)
On February 26 and February 29, we had two incidents related to a background job service that caused processing delays to GitHub services. The incident on February 26 lasted for 63 minutes, while the incident on February 28 lasted for 142 minutes.
The incident on February 26 was related to capacity constraints with our job queuing service and a failure of our automated failover system. Users experienced delays in Webhooks, GitHub Actions, and UI updates (for example, a delay in UI updates on pull requests). We mitigated the incident by manually failing over to our secondary cluster. No data was lost in the process.
The incident on February 29 also caused processing delays to Webhooks, GitHub Actions and GitHub Issues services, with 95% of the delays occurring in a 22-minute window between 11:05 and 11:27 UTC. At 9:32 UTC, our automated failover successfully routed traffic, but an improper restoration to the primary at 10:32 UTC caused a significant increase in queued jobs until a correction was made at 11:21 UTC and healthy services began burning down the backlog until full restoration at 11:27 UTC.
To prevent recurrence of the incidents in the short term, we have completed three significant improvements in the areas of better automation, increasing the reliability of our fallback process, and expanding the capacity of our background job queuing services based on these two incidents. For the longer term, we have a more significant effort already in progress to improve the overall scalability and reliability of our job processing platform.
Please follow our status page for real-time updates on status changes and post-incident recaps. To learn more about what we’re working on, check out the GitHub Engineering Blog.
The post GitHub Availability Report: February 2024 appeared first on The GitHub Blog.
]]>The post Exploring an increase in circumvention claims in our transparency data appeared first on The GitHub Blog.
]]>If you look at the circumvention claims chart of the Digital Millennium Copyright Act (DMCA) takedowns section, you’ll notice that there was an abrupt increase in DMCA notices for alleged circumvention in 2022. What caused this change?
As shown above, we processed 365 notices that alleged circumvention in 2022 and 406 notices in 2023 compared to just 92 notices over the entire year of 2021. In terms of average notices per month, that’s more than four times the volume (7.67 per month in 2021 vs. 33.83 per month in 2023).
A particularly observant chart-reader might be wondering why the slope of the line changed abruptly at the end of September 2021 from ~2 per month to ~30 per month.
On September 29, 2021, we updated our DMCA takedown submission form with questions related to circumvention. We made this change because DMCA circumvention claims typically require more extensive review, and marking takedown requests as circumvention claims allows us to triage them appropriately.
We anticipated that making this change could result in more submitters alleging circumvention, so shortly before the form update, we began adding annotations if we processed a circumvention-alleging takedown notice for reasons other than circumvention.
Breaking out the notices that alleged circumvention into notices we processed due to circumvention vs. those we processed on other grounds—such as for violating our Acceptable Use Policies—it appears that while significantly more notices we process allege circumvention, the rate at which we process takedown notices because of circumvention hasn’t accelerated.
Under our developer-focused approach to the DMCA, every takedown notice we receive that contains a credible circumvention claim and can’t be processed on other grounds, such as a valid copyright infringement claim, or a violation of our Acceptable Use Policies, is reviewed by a team of lawyers and engineers.
While this form change has resulted in an increase in circumvention claims and, consequently, time spent reviewing these claims, this process is an important component of our commitment to developers. GitHub handles DMCA claims with a goal to maximize the availability of code by limiting disruption for legitimate projects. Accordingly, we designed our DMCA Takedown Policy to safeguard developer interests against overreaching and ambiguous takedown requests. Each time we receive a valid DMCA takedown notice, we redact personal information, as well as any reported URLs where we were unable to determine there was a violation. We then post the notice to a public DMCA repository, where curious readers can find the redacted text of these notices, parse this data with regexes, and create charts like those in this deep dive. If you don’t want to do bespoke data analysis to classify circumvention claims, we plan to include this in a future transparency center update.
The DMCA generally makes it unlawful to circumvent technological measures used to prevent unauthorized access to copyrighted works, but it also establishes a triennial rulemaking process where users can petition for temporary exemptions to the prohibition of circumvention for noninfringing uses of copyrighted works. In the last rulemaking proceeding, GitHub filed comments advocating for a broader safe harbor for good faith security research. The ninth triennial proceedings are currently ongoing, and are considering interesting exemptions for software preservation, text and data mining, and generative AI research. We encourage developers to follow along and engage with DMCA reform as important stakeholders.
Do you have questions about our data or ideas for future data deep dives? Open an issue in the transparency center repository.
The post Exploring an increase in circumvention claims in our transparency data appeared first on The GitHub Blog.
]]>The post Hard and soft skills for developers coding in the age of AI appeared first on The GitHub Blog.
]]>As AI continues to shape the development landscape, developers are navigating a new frontier—not one that will make their careers obsolete, but one that will require their skills and instincts more than ever.
Sure, AI is revolutionizing software development, but that revolution ultimately starts and stops with developers. That’s because these tools need to have a pilot in control. While they can improve the time to code and ship, they can’t serve as a replacement for human oversight and coding abilities.
We recently conducted research into the evolving relationship between developers and AI tools and found that AI has the potential to alleviate the cognitive burden of complex tasks for developers. Instead of being used solely as a second pair of hands, AI tools can also be used more like a second brain, helping developers be more well-rounded and efficient.
In essence, AI can reduce mental strain so that developers can focus on anything from learning a new language to creating high-quality solutions for complex problems. So, if you’re sitting here wondering if you should learn how to code or how AI fits into your current coding career, we’re here to tell you what you need to know about your work in the age of AI.
While the media buzz around generative AI is relatively new, AI coding tools have been around —in some form or another—much longer than you might expect. To get you up to speed, here’s a brief timeline of the AI-powered tools and techniques that have paved the way for the sophisticated coding tools we have today:
1950s: Autocoder was one of the earliest attempts at automatic coding. Developed in the 1950s by IBM, Autocoder translated symbolic language into machine code, streamlining programming tasks for early computers.
1958: LISP, one of the oldest high-level programming languages created by John McCarthy, introduced symbolic processing and recursive functions, laying the groundwork for AI programming. Its flexibility and expressive power made it a popular choice for AI research and development.
(defun factorial (n)
(if (<= n 1)
1
(* n (factorial (- n 1)))))
This function calculates the factorial of a non-negative integer ‘n’ in LISP. If ‘n’ is 0 or 1, the factorial is 1. Otherwise, it recursively multiplies ‘n’ by the factorial of n-1 until ‘n’ reaches 1.
1970: SHRDLU, developed by Terry Winograd at MIT, was an early natural language understanding program that could interpret and respond to commands in a restricted subset of English, and demonstrated the potential for AI to understand and generate human language.
SHRDLU, operating in a block world, aimed to understand and execute natural language instructions for manipulating virtual objects made of various shaped blocks.
[Source: Cryptlabs]
1980s: In the 1980s, code generators, such as The Last One, emerged as tools that could automatically generate code based on user specifications or predefined templates. While not strictly AI-powered in the modern sense, they laid the foundation for later advancements in code generation and automation.
“Personal Computer” magazine cover from 1982 that explored the program, The Last One.
[Source: David Tebbutts]
1990s: Neural network–based predictive models were increasingly applied to code-related tasks, such as predicting program behavior, detecting software defects, and analyzing code quality. These models leveraged the pattern recognition capabilities of neural networks to learn from code examples and make predictions.
2000s: Refactoring tools with AI capabilities began to emerge in the 2000s, offering automated assistance for restructuring and improving code without changing its external behavior. These tools used AI techniques to analyze code patterns, identify opportunities for refactoring, and suggest appropriate refactorings to developers.
These early AI-powered coding tools helped shape the evolution of software development and set the stage for today’s AI-driven coding assistance and automation tools, which continue to evolve seemingly every day.
Initially, AI tools were primarily confined to the integrated development environment (IDE), aiding developers in writing and refining code. But now, we’re starting to see AI touch every part of the software development lifecycle (SDLC), which we’ve found can increase productivity, streamline collaboration, and accelerate innovation for engineering teams.
In a 2023 survey of 500 U.S.-based developers, 70% reported experiencing significant advantages in their work, while over 80% said these tools will foster greater collaboration within their teams. Additionally, our research revealed that developers, on average, complete tasks up to 55% faster when using AI coding tools.
Here’s a quick look at where modern AI-powered coding tools are and some of the technical benefits they provide today:
There are two different subsets of skills that can help developers as they begin to incorporate AI tools into their development workflows: technical skills and soft skills. Having both technical chops and people skills is super important for developers when they’re diving into AI projects—they need to know their technical skills to make those AI tools work to their advantage, but they also need to be able to work well with others, solve problems creatively, and understand the big picture to make sure the solutions they come up with actually hit the mark for the folks using them.
Let’s take a look at those technical skills first.
Prompt engineering involves crafting well-designed prompts or instructions that guide the behavior of AI models to produce desired outputs or responses. It can be pretty frustrating when AI-powered coding assistants don’t generate a valuable output, but that can often be quickly remedied by adjusting how you communicate with the AI. Here are some things to keep in mind when crafting natural language prompts:
AI is helpful, but it isn’t perfect. While LLMs are trained on large amounts of data, they don’t inherently understand programming concepts the way humans do. As a result, the code they generate may contain syntax errors, logic flaws, or other issues. That’s why developers need to rely on their coding competence and organizational knowledge to make sure that they aren’t pushing faulty code into production.
For a successful code review, you can start out by asking: does this code change accomplish what it is supposed to do? From there, you can take a look at this in-depth checklist of more things to keep in mind when reviewing AI-generated code suggestions.
With AI’s capabilities, developers can now generate and automate tests with ease, making their testing responsibilities less manual and more strategic. To ensure that the AI-generated tests cover critical functionality, edge cases, and potential vulnerabilities effectively, developers will need a strong foundational knowledge of programming skills, testing principles, and security best practices. This way, they’ll be able to interpret and analyze the generated tests effectively, identify potential limitations or biases in the generated tests, and augment with manual tests as necessary.
Here’s a few steps you can take to assess the quality and reliability of AI-generated tests:
For those beginning their coding journey, check out the GitHub Learning Pathways to gain deeper insights into testing strategies and security best practices with GitHub Actions and GitHub Advanced Security.
You can also bolster your security skills with this new, open source Secure Code Game 🎮.
As developers leverage AI to build what’s next, having soft skills—like the ability to communicate and collaborate well with colleagues—is becoming more important than ever.
Let’s take a more in-depth look at some soft skills that developers can focus on as they continue to adopt AI tools:
Did you know that prompt engineering best practices just might help you build your communication skills with colleagues? Check out this thought piece from Harvard Business Review for more insights. |
Sharpening these soft skills can ultimately augment a developer’s technical expertise, as well as enable them to work more effectively with both their colleagues and AI tools.
As AI continues to evolve, it’s not just changing the landscape of software development; it’s also poised to revolutionize how developers learn and write code. AI isn’t replacing developers—it’s complementing their work, all while providing them with the opportunity to focus more on coding and building their skill sets, both technical and interpersonal.
If you’re interested in improving your skills along your AI-powered coding journey, check out these repositories to start building your own AI based projects. Or you can test out GitHub Copilot, which can help you learn new programming languages, provide coding suggestions, and ask important coding questions right in your terminal.
The post Hard and soft skills for developers coding in the age of AI appeared first on The GitHub Blog.
]]>The post How GitHub uses merge queue to ship hundreds of changes every day appeared first on The GitHub Blog.
]]>At GitHub, we use merge queue to merge hundreds of pull requests every day. Developing this feature and rolling it out internally did not happen overnight, but the journey was worth it—both because of how it has transformed the way we deploy changes to production at scale, but also how it has helped improve the velocity of customers too. Let’s take a look at how this feature was developed and how you can use it, too.
Merge queue is generally available and is also now available on GitHub Enterprise Server! Find out more. |
In 2020, engineers from across GitHub came together with a goal: improve the process for deploying and merging pull requests across the GitHub service, and specifically within our largest monorepo. This process was becoming overly complex to manage, required special GitHub-only logic in the codebase, and required developers to learn external tools, which meant the engineers developing for GitHub weren’t actually using GitHub in the same way as our customers.
To understand how we got to this point in 2020, it’s important to look even further back.
By 2016, nearly 1,000 pull requests were merging into our large monorepo every month. GitHub was growing both in the number of services deployed and in the number of changes shipping to those services. And because we deploy changes prior to merging them, we needed a more efficient way to group and deploy multiple pull requests at the same time. Our solution at this time was trains. A train was a special pull request that grouped together multiple pull requests (passengers) that would be tested, deployed, and eventually merged at the same time. A user (called a conductor) was responsible for handling most aspects of the process, such as starting a deployment of the train and handling conflicts that arose. Pipelines were added to help manage the rollout path. Both these systems (trains and pipelines) were only used on our largest monorepo and were implemented in our internal deployment system.
Trains helped improve velocity at first, but over time started to negatively impact developer satisfaction and increase the time to land a pull request. Our internal Developer Experience (DX) team regularly polls our developers to learn about pain points to help inform where to invest in improvements. These surveys consistently rated deployment as the most painful part of the developer’s daily experience, highlighting the complexity and friction involved with building and shepherding trains in particular. This qualitative data was backed by our quantitative metrics. These showed a steady increase in the time it took from pull request to shipped code.
Trains could also grow large, containing the changes of 15 pull requests. Large trains frequently “derailed” due to a deployment issue, conflicts, or the need for an engineer to remove their change. On painful occasions, developers could wait 8+ hours after joining a train for it to ship, only for it to be removed due to a conflict between two pull requests in the train.
Trains were also not used on every repository, meaning the developer experience varied significantly between different services. This led to confusion when engineers moved between services or contributed to services they didn’t own, which is fairly frequent due to our inner source model.
In short, our process was significantly impacting the productivity of our engineering teams—both in our large monorepo and service repositories.
By 2020, it was clear that our internal tools and processes for deploying and merging across our repositories were limiting our ability to land pull requests as often as we needed. Beyond just improving velocity, it became clear that our new solution needed to:
The merge queue project began as part of an overall effort within GitHub to improve availability and remove friction that was preventing developers from shipping at the frequency and level of quality that was needed. Initially, it was only focused on providing a solution for us, but was built with the expectation that it would eventually be made available to customers.
By mid-2021, a few small, internal repositories started testing merge queue, but moving our large monorepo would not happen until the next year for a few reasons.
For one, we could not stop deploying for days or weeks in order to swap systems. At every stage of the project we had to have a working system to ship changes. At a maximum, we could block deployments for an hour or so to run a test or transition. GitHub is remote-first and we have engineers throughout the world, so there are quieter times but never a free pass to take the system offline.
Changing the way thousands of developers deploy and merge changes also requires lots of communication to ensure teams are able to maintain velocity throughout the transition. Training 1,000 engineers on a new system overnight is difficult, to say the least.
By rolling out changes to the process in phases (and sometimes testing and rolling back changes early in the morning before most developers started working) we were able to slowly transition our large monorepo and all of our repositories responsible for production services onto merge queue by 2023.
Merge queue has become the single entry point for shipping code changes at GitHub. It was designed and tested at scale, shipping 30,000+ pull requests with their associated 4.5 million CI runs, for GitHub.com
before merge queue was made generally available.
For GitHub and our “deploy the merge process,” merge queue dynamically forms groups of pull requests that are candidates for deployment, kicks off builds and tests via GitHub Actions, and ensures our main branch is never updated to a failing commit by enforcing branch protection rules. Pull requests in the queue that conflict with one another are automatically detected and removed, with the queue automatically re-forming groups as needed.
Because merge queue is integrated into the pull request workflow (and does not require knowledge of special ChatOps commands, or use of labels or special syntax in comments to manage state), our developer experience is also greatly improved. Developers can add their pull request to the queue and, if they spot an issue with their change, leave the queue with a single click.
We can now ship larger groups without the pitfalls and frictions of trains. Trains (our old system) previously limited our ability to deploy more than 15 changes at once, but now we can now safely deploy 30 or more if needed.
Every month, over 500 engineers merge 2,500 pull requests into our large monorepo with merge queue, more than double the volume from a few years ago. The average wait time to ship a change has also been reduced by 33%. And it’s not just numbers that have improved. On one of our periodic developer satisfaction surveys, an engineer called merge queue “one of the best quality-of-life improvements to shipping changes that I’ve seen a GitHub!” It’s not a stretch to say that merge queue has transformed the way GitHub deploys changes to production at scale.
Merge queue is available to public repositories on GitHub.com owned by organizations and to all repositories on GitHub Enterprise (Cloud or Server).
To learn more about merge queue and how it can help velocity and developer satisfaction on your busiest repositories, see our blog post, GitHub merge queue is generally available.
Interested in joining GitHub? Check out our open positions or learn more about our platform.
The post How GitHub uses merge queue to ship hundreds of changes every day appeared first on The GitHub Blog.
]]>The post GitHub Enterprise Server 3.12 is now generally available appeared first on The GitHub Blog.
]]>GitHub Enterprise Server 3.12 is now generally available. With this version, customers can choose how to best scale their security strategy, gain more control over deployments, and so much more.
Highlights of this version include:
Download GitHub Enterprise Server 3.12 now. For help upgrading, use the Upgrade Assistant to find the upgrade path from your current version of GitHub Enterprise Server (GHES) to this new version. |
Using environments in GitHub Actions lets you configure your deployment environments with protection rules and secrets in order to better ensure secure deployments. As of today, tag patterns are now generally available. This capability makes it easy to specify selected tags or tag patterns on your protected environments in order to add an additional layer of security and control to your deployments. For example, you can now define that only “Releases/*” tags can be deployed to your production environment. Learn more about securing environments using deployment protection rules.
This feature makes it easy for teams to define and enforce standard CI/CD practices in the form of rulesets across multiple repositories within their organization without needing to configure individual repositories. For anyone using the legacy required workflows feature, your workflows will be automatically migrated to rulesets. With rulesets, it’s easier than ever for organizations to ensure their team’s code is secure, compliant, and correct before being deployed to production. Check out our documentation to learn more about requiring workflows with rulesets.
Collaborative coding is essential for team productivity, but requires efficient branch management to avoid frustration and maintain velocity. Automated branch management, like merge queue, streamlines this process by ensuring compatibility, alerts developers of any issues, and allows teams to focus on coding without interruptions. With merge queue available in GHES, enterprises have a central platform for collaboration and the integrated tools for enterprise-level development.
With Dependabot, you can proactively manage security alerts to ensure high-priority items are surfaced. With user-configured alert rules, you can now tailor your security strategy to your specific risk tolerance and contextual needs, streamlining alert triage and remediation processes.
GitHub offers suggested rulesets curated for all users, automatically filtering out false positives for public repositories and suggestions for private ones. Dependabot’s rules engine empowers developers to automatically manage alerts, from auto-dismissing to reopening based on customizable criteria. Stay ahead of vulnerabilities with Dependabot, supported by GitHub’s continuously improved vulnerability patterns.
With this update, code scanning default setup will change how languages are analyzed in repositories. No longer will repositories need to manually select compiled languages for inclusion in the default setup configuration. Instead, the system will automatically attempt to analyze all CodeQL supported languages. The “edit configuration” page allows users to see which languages are included in each configuration and apply any customization that may be required. This feature will be available at both the repository and organization levels, guaranteeing the best setup for your repository.
Secret scanning goes beyond provider patterns to detect critical security vulnerabilities like HTTP authentication headers, database connection strings, and private keys. Simply enable the “Scan for non-provider patterns” option in your repository or organization’s security settings to increase your defenses. With detected secrets conveniently categorized under a new “Other” tab on the alert list, you can ensure thorough protection for your most sensitive information. Stay ahead of threats and safeguard your data with our comprehensive secret scanning capabilities.
Markdown serves as a fundamental tool. It is used for documentation, notes, comments, and decision records. GitHub is now taking it one step further with the addition of a Markdown extension to highlight text, signaling that certain information has different meaning than another.
We’ve introduced the redesigned global navigation for GitHub.com, featuring a suite of enhancements tailored to elevate user experience and efficiency. Our latest updates to GHES aim to streamline navigation, enhance accessibility, and boost performance. With improved wayfinding through breadcrumbs and easy access to essential repositories and teams from any location, navigating GitHub has never been more seamless.
Our latest feature update on GitHub Projects is designed to enhance project management streamlining project creation and foster collaboration within teams. With these updates, you can now swiftly create, share, and utilize project templates within your organizations, simplifying the process of starting new projects.
To learn more about GitHub Enterprise Server 3.12, read the release notes or download it now.
Not using GHES already? Start a free trial to innovate faster with the developer experience platform companies know and love.
The post GitHub Enterprise Server 3.12 is now generally available appeared first on The GitHub Blog.
]]>