intro

Git patch is a feature of the Git version control system that allows developers to create and apply patches to code changes. A patch is a file that contains a set of changes made to a codebase, which can then be applied to another codebase to replicate those changes.

In this case study, we’ll look at a typical patch file and understand what it means line by line and how it can be abused.

Understand a patch file

Let’s look at a simple git patch to begin with

# create and init a repo
> mkdir repo
> cd repo
> git init

# create a file and make the first commit
> touch file.txt
> echo 'this is some content' > file.txt
> git add . && git commit -m 'update'

# update the file.txt
> echo 'somthing extra' >> file.txt
# add an extra file.txt
> touch extra_file.txt
> git add . && git commit -m 'update'

# add a symlink
> mkdir /tmp/test
> ln -s /tmp/test symlink
> git add . && git commit -m 'add symlink'

# rename a file
> mv file.txt renamed.txt
> git add . && git commit -m 'rename'

The diff would look like this, note that the content in the diff file won’t necessarily follow the order of git operations. It’s essentially a file for the git tools to understand, not intended for human to directly work with.

> git diff dfffa90977c9e18832a63e22f9daebfe315fbc08 37a5b7ddb13a8560839b5f7e7c9119bf83cdfff8
# create a file and add extra content
diff --git a/file.txt b/file.txt
index f95c11d..7df7c67 100644
--- a/file.txt
+++ b/file.txt
@@ -1 +1,2 @@
 this is some content
+something extra

# add a new file
diff --git a/extra_file.txt b/extra_file.txt
new file mode 100644
index 0000000..e69de29

# add a symlink
diff --git a/symlink b/symlink
new file mode 120000
index 0000000..b249f1a
--- /dev/null
+++ b/symlink
@@ -0,0 +1 @@
+/tmp/test

# rename a file
diff --git a/file.txt b/renamed.txt
similarity index 56%
rename from file.txt
rename to renamed.txt
\ No newline at end of file

Let’s break down the diff and examine each line

diff --git a/file.txt b/file.txt

This is the Git diff header

  • diff --git is just a syntax notion of a Git-specific diff in Unix command style.
  • a/file.txt and b/file.txt are the files being compared, the leading directory notions a/ and b/ are not actual directories, they are just conventional notions to distinguish files in case there is a rename. This is case, there isn’t a rename, hence both files are still using the same name.
index f95c11d..7df7c67 100644

This is an extended header line that gives information from the Git index regarding this file:

  • f95c11d and 7df7c67 are the blob IDs of the A and B versions of the file contents being compared
  • 100644 is the mode bits, indicating this is a regular file, i.e not executable and not a symbolic link
  • .. notion between the blob IDs is just as a separator
--- a/file.txt
+++ b/file.txt

This is the traditional unified diff header, it shows the files being compared and the direction of the changes

  • --- shows lines in the A version but missing from the B version
  • +++ shows lines in the B version but missing from the A version
  • If there is file create or remove performed, /dev/null will be used to indicate the absence.
@@ -1 +1,2 @@
 this is some content
+something extra

This is a difference section (i.e change hunk, or chunk)

  • @@ -R,r +R,r @@ notion indicates the line number and length this chunk in the A and B versions. Those are two ranges, the one with the - is the range for the chunk in the original file, and the one with the + the range in the new file. The R designates the line number where the diff operation is started.
    • Every time you remove a line, the +r number will be smaller than -r.
    • Every time you add a line, the +r number will be bigger than -r
    • Changing a line will add 0 to the +r number. (same scope of lines)
  • The subsequent lines beginning with a space are context: they appear as shown in both versions of the file. The lines beginning with - and + signs have the meanings mentioned above.
# add a new file
diff --git a/extra_file.txt b/extra_file.txt
new file mode 100644
index 0000000..e69de29

With the line by line explaination from above, this chuck should be easy to understand.

# add a symlink
diff --git a/symlink b/symlink
new file mode 120000
index 0000000..b249f1a
--- /dev/null
+++ b/symlink
@@ -0,0 +1 @@
+/tmp/test

Adding a symlink is similar to adding a new file, note the differences in file mode 1200000 and the use of --- /dev/null

# rename a file
diff --git a/file.txt b/renamed.txt
similarity index 56%
rename from file.txt
rename to renamed.txt

The similarity index is the percentage of unchanged lines, and the dissimilarity index is the percentage of changed lines. It is a rounded down integer, followed by a percent sign. The similarity index value of 100% is thus reserved for two equal files, while 100% dissimilarity means that no line from the old file made it into the new one.

example

Take the following diff as an example, this diff will rename an existing symlink to symlink2 and attempt to create a file called poc under the symlink2’s path and populate it with content aaaaaaaa.

Of course this is not possible due to security implications and had been fixed in v2.39.2, v2.38.4, v2.37.6, v2.36.5, v2.35.7, v2.34.7, v2.33.7, v2.32.6, v2.31.7, and v2.30.8. https://www.cvedetails.com/cve/CVE-2023-23946/

diff --git a/symlink b/symlink2
similarity index 100%
rename from symlink
rename to symlink2
--
diff --git /dev/null b/symlink2/poc
new file mode 100644
index 0000000..b249f1a
--- /dev/null
+++ b/symlink2/poc
@@ -0,0 +1 @@
+aaaaaaaa

Support meowmeow

If you find this article useful, please support: https://www.buymeacoffee.com/meowmeowattack