-
Install Visual Studio Code.
-
Install the CodeQL extension for Visual Studio Code.
-
(Optionally) Install Docker if you want to build your own CodeQL database.
-
(Optionally) Install the CodeQL CLI if you want to build your own CodeQL database.
-
Clone this repository recursively:
git clone --recursive https://github.com/rvermeulen/codeql-workshop-elements-of-syntactical-program-analysis-cpp
-
Install the CodeQL pack dependencies using the command
CodeQL: Install Pack Dependencies
and selectexercises
andsolutions
. -
Download the prebuilt database or build the database using the predefined Makefile by running
make
. -
Select the Vulnerable Linux Driver database as the current database by right-clicking on the file
vulnerable-linux-driver-db.zip
in the File explorer and running the commandCodeQL: Set Current Database
.
In this workshop you will learn how to describe syntactical elements of the C/C++ programming language. With the goal of describing the user-mode entry point of the intentionally vulnerable Linux driver you will:
- Discover how QL represents C/C++ program elements.
- Learn to query program elements.
- Learn how to encapsulate descriptions of program elements using QL classes.
This workshop focusses on the syntactical parts. Some parts in this workshop can be generalized using more advanced techniques, such as dataflow analysis, that are covered in other workshops.
The intentionally vulnerable Linux driver project implements a Miscellaneous Character Driver to expose multiple vulnerabilities to learn about Kernel driver exploitation.
The miscellaneous character driver was designed for use cases that require a small device driver to support custom hardware or software hacks. To register or unregister a miscellaneous driver, the misc driver exports two functions for user modules, that can be found in the header linux/miscdevice.h, called misc_register and misc_unregister.
With the misc_register
function as the starting point we will traverse function calls, expressions, structure definitions, and variable initializations to find the user module entrypoint. This entrypoint will be the start of future workshops that will discuss the vulnerabilities that can be found in this intentionally vulnerable Linux driver.
Find all the function calls in the program by implementing Exercise1.ql.
Hints
- The class
FunctionCall
can be used to reason about all the function calls in the program.
A solution can be found in the query Exercise1.ql
Find all the function calls to the function misc_register
by implementing Exercise2.ql.
Hints
- The class
FunctionCall
provides the member predicategetTarget
to reason about the called function. - The class
Function
provides the member predicategetName
to get the name of the function.
A solution can be found in the query Exercise2.ql
Recall that predicates and classes allow you to encapsulate logical conditions in a reusable format.
Convert your solution to Exercise2.ql into a class in Exercises3.ql by replacing the none formula in the characteristic predicate of the MiscRegisterFunction
class.
Besides relying on the name, try to add another property to distinguish the correct function.
Hints
- Each program element represented by the class
Element
can be related to the primary file the element occurs in using the member predicategetFile
. - Each program element has an absolute path that can be accessed using the member predicate
getAbsolutePath
on the classFile
. - The QL string type provides builtins such as
matches
andregexpMatch
to match patterns in strings. Thematches
builtin member predicate interprets_
to match any single character and%
to match any sequences of characters in the provided pattern.
A solution can be found in the query Exercise3.ql
The definition of the driver is passed as a parameter to the misc_register
function.
Obtain the argument to the call, determine the arguments type and primary QL class by implementing Exercise4.ql.
Hints
- The class
FunctionCall
provides the member predicategetArgument
to get a provided argument by index. - Each expression represented by the class
Expr
has a type that can be retrieved with the member predicategetType
. - Each program element represented by the class
Element
has a member predicategetPrimaryQlClass
that returns the QL class that is the most precise syntactic category the element belongs to.
A solution can be found in the query Exercise4.ql
The definition of the miscellaneous driver is defined by the structure miscdevice
.
Implement the characteristic predicate of the class MiscDeviceStruct
in
Exercise5.ql so we can reason about its use.
Hints
- The class
Struct
inherits the member predicategetName
from the classUserType
that returns the name of the struct. - Each program element represented by the class
Element
can be related to the primary file the element occurs in using the member predicategetFile
. - Each program element has an absolute path that can be accessed using the member predicate
getAbsolutePath
on the classFile
. - The QL string type provides builtins such as
matches
andregexpMatch
to match patterns in strings. Thematches
builtin member predicate interprets_
to match any single character and%
to match any sequences of characters in the provided pattern.
A solution can be found in the query Exercise5.ql
Now that we can reason about the structure miscdevice
we can look for all it instantiations.
Implement the characteristic predicate of the class MiscDeviceDefinition
in
Exercise6.ql so we use it to find all its instances.
Hints
- The class
Variable
has a member predicategetType
that gets the type of this variable.
A solution can be found in the query Exercise6.ql
The instantiation vuln_device
initializes 3 members of the miscdevice
structure.
Find the type of the third field initialized with &vuln_fops
by implementing
Exercise7.ql.
Hints
- The class
Struct
inherits the member predicategetAMember
from the classClass
that gets the zero-based indexed member declared in the struct. - The class
Field
inherits the member predicategetType
from the classMemberVariable
that returns the type of the field.
A solution can be found in the query Exercise7.ql
With knowledge of the type of third field om the miscdevice
structure we can now identify all file operation definitions such as vuln_fops
.
Implement the characteristic predicates for the class FileOperationsStruct
and FileOperationsDefinition
in Exercise8.ql.
Hints
- The class
Struct
inherits the member predicategetName
from the classUserType
that returns the name of the struct. - Each program element represented by the class
Element
can be related to the primary file the element occurs in using the member predicategetFile
. - Each program element has an absolute path that can be accessed using the member predicate
getAbsolutePath
on the classFile
. - The QL string type provides builtins such as
matches
andregexpMatch
to match patterns in strings. Thematches
builtin member predicate interprets_
to match any single character and%
to match any sequences of characters in the provided pattern.
A solution can be found in the query Exercise8.ql
The single file operation definition vuln_fops
is initialized with, among others, a function pointer for the field unlocked_ioctl
.
This is the function that is invoked when a user-mode application performs the ioctl
system call to communicate with the driver.
Extend the class FileOperationsDefinition
with a member predicate getUnlockedIoctl
that returns a Function
with which the file operations definition is initialized in
Exercise9.ql.
Hints
- The class
Variable
has the member predicategetAnAssignedValue
that returns anExpr
representing an expression that is assigned to this variable somewhere in the program. - The class
Field
inherits the member predicatehasName
from the classDeclaration
that holds if the field has the provided name. - The class
ClassAggregrateLiteral
has the member predicategetFieldExpr
that returns anExpr
that is part of the aggregrate literal that is used to initialize the provided field.
A solution can be found in the query Exercise9.ql
We have successfully identified the miscellaneous driver definition, the file operations definition, and linked the ioctl handler to the file operations definition.
Extend the class MiscDeviceDefinition
with the member predicate getFileOperations
that returns a FileOperationsDefinition
that the miscellaneous driver definition is initialized with in Exercise10.ql.
Hints
- The class
Variable
has the member predicategetAnAssignedValue
that returns anExpr
representing an expression that is assigned to this variable somewhere in the program. - The class
Field
inherits the member predicatehasName
from the classDeclaration
that holds if the field has the provided name. - The class
ClassAggregrateLiteral
has the member predicategetFieldExpr
that returns anExpr
that is part of the aggregrate literal that is used to initialize the provided field. - A class can be cast to a subclass using the syntax
variable.(Class).predicate()
. For example, to cast an expressionexpr
to aAddressOfExpr
to get an operand of the expression you can use the syntaxexpr.(AddressOfExpr).getOperand()
. - The class
AddressOfExpr
that represents the expression taking the address&expr
has a member predicategetOperand
that returns the expression of which the address is taken. - The class
Variable
has a member predicategetAnAccess
that returns all the access to this variable.
A solution can be found in the query Exercise10.ql
In this final exercise we have to relate the call to misc_register
to the miscellaneous driver definition so we can find the driver's entrypoint for user-mode.
Implement the characteristic predicate for the class MiscDriverUserModeEntry
in Exercise11.ql that relates the classes MiscRegisterFunction
, MiscDeviceDefinition
, and FileOperationsDefinition
to obtain the unlocked ioctl handler function.
Hints
- The class
Function
has a member predicategetACallToThisFunction
that returns all the function call to this function. - The class
FunctionCall
inherits the member predicategetArgument
from the classCall
that returns the nth argument for this call. - A class can be casted to a subclass using the syntax
variable.(Class).predicate()
. For example, to cast an expressionexpr
to aAddressOfExpr
to get an operand of the expression you can use the syntaxexpr.(AddressOfExpr).getOperand()
. - The class
Variable
has a member predicategetAnAccess
that returns all the access to this variable.
A solution can be found in the query Exercise11.ql