Oxford Dic
Oxford Dic
Oxford Dic
Linaro
1 Getting started 3
1.1 About OP-TEE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2 Coding standards . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.3 Contribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.4 Contact . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.5 Disclosure policy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.6 License headers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
1.7 Platforms supported . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
1.8 Presentations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2 Architecture documentation 19
2.1 Core . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.2 Cryptographic implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
2.3 File structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
2.4 GlobalPlatform API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
2.5 Libraries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
2.6 Porting guidelines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
2.7 Secure boot . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
2.8 Secure storage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
2.9 Trusted Applications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
4 Debugging techniques 83
4.1 Abort dumps / call stack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
4.2 Benchmark framework . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
4.3 Gprof . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
4.4 GDB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
4.5 JTAG . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
i
5.1 Frequently Asked Questions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
ii
OP-TEE Documentation
Contents 1
OP-TEE Documentation
2 Contents
CHAPTER 1
Getting started
OP-TEE is a Trusted Execution Environment (TEE) designed as companion to a non-secure Linux kernel running on
Arm; Cortex-A cores using the TrustZone technology. OP-TEE meets the TEE System Architecture and provides the
TEE Internal Core API v1.1.x to Trusted Applications and the TEE Client API v1.0, all as defined by the GlobalPlat-
form API specifications.
The non-secure OS is referred to as the Rich Execution Environment (REE) in TEE specifications. It is typically a
Linux OS flavor as a GNU/Linux distribution or the AOSP.
OP-TEE is designed primarily to rely on the Arm TrustZone technology as the underlying hardware isolation mech-
anism. However, it has been structured to be compatible with any isolation technology suitable for the TEE concept
and goals, such as running as a virtual machine or on a dedicated CPU.
The main design goals for OP-TEE are:
• Isolation - the TEE provides isolation from the non-secure OS and protects the loaded Trusted Applications
(TAs) from each other using underlying hardware support,
• Small footprint - the TEE should remain small enough to reside in a reasonable amount of on-chip memory as
found on Arm based systems,
• Portability - the TEE aims at being easily pluggable to different architectures and available HW and has to
support various setups such as multiple client OSes or multiple TEEs.
3
OP-TEE Documentation
1.1.2 History
OP-TEE was initially developed by ST-Ericsson (and later on by STMicroelectronics), but this was before OP-TEE got
the name “OP-TEE” and was turned into an open source project. Back then it was a closed source and a proprietary
TEE solution. In 2013, ST-Ericsson obtained GlobalPlatform’s compliance qualification with this implementation,
proving that the APIs were behaving as expected in the GlobalPlatform specifications.
Later on the same year (2013) Linaro was about to form Security Working Group (SWG) and one of the initial key
tasks for SWG was to work on an open source TEE solution. After talking to various TEE vendors Linaro ended up
working with STMicroelectronics TEE solution. But before being able to open source it there was a need to replace
some proprietary components with open source components. For a couple of months Linaro/SWG together with
engineers from STMicroelectronics re-wrote major parts (crypto library, secure monitor, build system etc), cleaned up
the solution by enforcing Coding standards, running checkpatch etc.
June 12 2014 was the day when OP-TEE was “born” as an open source project. At that day the OP-TEE team pushed
the first commit to GitHub. A bit after this Linaro also made a press release about this. That press release contains a
bit more information. At the first year as an open source project it was owned by STMicroelectronics but maintained
by Linaro and STMicroelectronics. In 2015 there was an ownership transfer of OP-TEE from STMicroelectronics
to Linaro and since then it has been Linaro who is the primary owner and maintainer of the project. But for the
maintenance part, it has become a shared responsibility between Linaro, Linaro members and other companies who
are using OP-TEE.
In this project we are trying to adhere to the same coding convention as used in the Linux kernel (see CodingStyle).
We achieve this by running checkpatch from Linux kernel. However there are a few exceptions that we had to make
since the code also follows GlobalPlatform standards. The exceptions are as follows:
• CamelCase for GlobalPlatform types are allowed.
• And we also exclude checking third party code that we might use in this project, such as LibTomCrypt, MPA,
newlib (not in this particular git, but those are also part of the complete TEE solution, see repository-structure).
The reason for excluding and not fixing third party code is because we would probably deviate too much from
upstream and therefore it would be hard to rebase against those projects later on and we don’t expect that
it is easy to convince other software projects to change coding style. Automatic variables should always be
initialized. Mixed declarations and statements are allowed, and may be used to avoid assigning useless values.
Please leave one blank line before and after such declarations.
Regarding the checkpatch tool, it is not included directly into this project. Please use checkpatch.pl from the Linux
kernel git in combination with the local checkpatch script.
1.3 Contribute
Contributions to OP-TEE are managed by the OP-TEE core team and anyone can contribute to OP-TEE as long as it is
understood that it will require a sign-off. The sign-off is a simple line at the end of the explanation for the patch, which
certifies that you wrote it or otherwise have the right to pass it on as an open-source patch (see below). You thereby
assure that you have read and are following the rules stated in the Developer Certificate of Origin as
stated below.
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
660 York Street, Suite 102,
San Francisco, CA 94110 USA
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
We have borrowed this procedure from the Linux kernel project to improve tracking of who did what, and for legal
reasons.
To sign-off a patch, just add a line in the commit message saying:
1.3. Contribute 5
OP-TEE Documentation
1.3.2 GitHub
This section describes how to use GitHub for OP-TEE development and contributions.
Setting up an account
You do not need to own a GitHub account in order to clone a repository. But if you want to contribute, you need to
create an account at GitHub first. Note that a free plan is sufficient to collaborate.
SSH is recommended to access your GitHub repositories securely and without supplying your username and password
each time you pull or push something. To configure SSH for GitHub, please refer to Connecting to GitHub with SSH.
Forking
Only owners of the OP-TEE projects have write permission to the Git repositories of those projects. Contributors
should fork OP-TEE/*.git and/or linaro-swg/*.git into their own account, then work on this forked repos-
itory. The complete documentation about forking can be found at fork a repo.
Note that the fork only has to be performed once.
When you want to submit a patch to the OP-TEE project, you are supposed to create a pull request to the git where
you forked your git from. How that is done using GitHub is explained at the GitHub pull request page.
Commit messages
• The subject line should explain what the patch does as precisely as possible. It is usually prefixed with key-
words indicating which part of the code is affected, but not always. Avoid lines longer than 80 characters.
• The commit description should give more details on what is changed, and explain why it is done. Indication
on how to enable and use some particular feature can be useful, too. Try to limit line length to 72 characters,
except when pasting some error message (compiler diagnostic etc.). Long lines are allowed to accommodate
URLs, too (preferably use URLs in a Fixes: or Link: tag).
• The commit message must end with a blank line followed by some tags, including your Signed-off-by:
tag. By applying such a tag to your commit, you are effectively declaring that your contribution follows the
terms stated by Developer Certificate of Origin (in the DCO section there is also a complete example).
• Other tags may be used, such as:
– Tested-by: Test R <[email protected]>
– Acked-by: Acke R <[email protected]>
– Suggested-by: Suggeste R <[email protected]>
– Reported-by: Reporte R <[email protected]>
• When citing a previous commit, whether it is in the text body or in a Fixes: tag, always use the format shown
above (12 hexadecimal digits prefix of the commit SHA1, followed by the commit subject in double quotes and
parentheses).
Review feedback
It’s very likely that you will get review comments from other OP-TEE users asking you to fix certain things etc. When
fixing review comments, do:
• Add patches on top of your existing branch. Do not squash and force push when fixing review comments.
• When all comments have been addressed, just write a simple messages in the comments field saying something
like “All comments have been addressed”. By doing so you will notify the maintainers that the fix might be
ready for review again.
Once you and reviewers have agreed on the patch set, which is when all the people who have commented on the pull
request have given their Acked-by: or Reviewed-by:, you need to consolidate your commits:
Use git rebase -i to squash the fixup commits (if any) into the initial ones. For instance, suppose the git log
--oneline for you contribution looks as follows when the review process ends:
<commit1> Do something
<commit2> Do something else
<commit3> [Review] Do something
<commit4> [Review] Do something
Add the proper tags (Acked-by: ..., Reviewed-by: ..., Tested-by: ...) to the commit mes-
sage(s), as provided by the people who reviewed and/or tested the patches.
Once rebase -i is done, you need to force-push (-f) to your GitHub branch in order to update the pull request
page.
$ git push -f
At this point, it is the project maintainer’s job to apply your patches to the master branch.
1.4 Contact
1.4.1 GitHub
Our preference is to use GitHub for communication. The reason for that is that it is an open source project, so there
should be no real reason to hide discussions from other people. GitHub also makes it possible for anyone to chime
in into discussion etc. So besides sending patches as pull requests on GitHub we also encourage people to use the
“issues” to report bugs, give suggestions, ask questions etc.
1.4. Contact 7
OP-TEE Documentation
Please try to use the “issues” in the relevant git. I.e., if you want to discuss something related to optee_client, then use
“issues” in optee_client and so on. If you have a general question etc about OP-TEE that doesn’t really belong to a
specific git, then please use issues in optee_os in that case.
1.4.2 Email
You can reach the core team core team by sending an email to <op-tee[at]linaro[dot]org>. However note that the team
consist of engineers from different companies, i.e, it not just Linaro engineers on that email address.
From time to time we are also using the “TEE-dev” mailinglist <tee-dev[at]lists[dot]linaro[dot]org>. It has mostly
been used when we have discussed and sent patches related to the TEE framework in Linux kernel.
For pure Linux kernel patches, please use the appropriate Linux kernel mailinglist, basically run the
get_maintainer.pl script to know where to send your patches.
$ cd <linux-kernel>
$ ./scripts/get_maintainer.pl drivers/tee/
1.4.3 IRC
Please send an email to the address mentioned above (not to TEE-dev). Don’t include any details at this point, just
mention that you’d like to report a security issue. An engineer from the core OP-TEE team will get back to you for
further communication and discussions about your findings. Please also read the Disclosure policy page and especially
the Reporting issues section, so you are aware of the rules we are following.
When a vulnerability has been reported (see Vulnerability reporting) to the core team, it is up to them to implement
mitigations and fixes as well as report back to stakeholders in a responsible way. This page describes the responsible
disclosure policy that applies to the OP-TEE project.
Note: The “core team” in Linaro (who owns the OP-TEE project) consists of engineers directly employed by Linaro
as well as engineers employed by companies who are members of Linaro.
1.5.1 Rules
To have some kind of ground to stand on we have defined a set of rules and conditions that applies both when it comes
to being a taker of information as well as being reporter of security issues. It should be noted that it is hard to write
rules that you can follow to 100%, since depending on the type of security issues being dealt with it might or might
not be possible for the core team and Linaro to re-distribute the information right away.
An example of when we couldn’t follow our rules and disclosure policy was when we got informed (under NDA)
about the Spectre and Meltdown issues (this was before it was public knowledge). That was considered so sensitive
that we weren’t even allowed to share or discuss this outside Linaro (employees).
But in general, we strive and try to do our best to follow the rules etc that have been defined on this particular page.
Receiving information
The one receiving information about and fixes related to OP-TEE security vulnerabilities must follow these rules:
1. The receiver of vulnerability information and/or security fixes shared by the core team and Linaro are not
allowed to share, re-distribute or otherwise spread knowledge about the issues and security fixes outside their
own company until the disclosure deadline has passed and the information is publicly available.
1.1. If the receiver still wants to share it with other people/companies he must first get approval from
the core team and Linaro to do so.
Reporting issues
The one reporting security vulnerabilities to the core team and Linaro are asked to do it under the conditions mentioned
below. It might seem like a long list, but we hope that it won’t scare people away from reporting issues. It’s mostly
common sense and also aims to rule out questions that otherwise might come to mind. In short it by default gives the
core team and Linaro the power to decide what to do with the reported issue if nothing else has been agreed between
them and the reporter.
1. If nothing else has been agreed between the reporter and the core team and Linaro, then the rules and information
as stated on this page applies.
1.1. This means that the core team and Linaro will re-distribute the information to the stakeholders
according to the plan described further down here.
1.2. This also means that patches etc will be submitted to the upstream project based on the proposed
disclosure day that will be given to the reporter after initial investigation.
2. By default, the information about the reported issue(s) will be shared within the core team (see the note about
the core team at the beginning of the page). If you as a reporter aren’t OK with that, then you must inform us
about that when reporting the issue.
3. By default, the core team and Linaro decides whether there should be a CVE created or not. If the reporter insist
on having a CVE created, then this should be expressed when doing the reporting.
4. The core team and Linaro have the rights to involve other experts to help us with mitigations and patches. If you
as a reporter aren’t OK with that, then you must inform us about that when reporting the issue.
5. Reporting security issues under NDA should be seen as a last resort thing. If/when that happens, then we will
come up with a mutual agreement on a disclosure plan.
6. It is appreciated if the reporter have estimated some initial severity scoring as described further down on this
page. This is mainly to get an indication whether we share the same view about the severity or not.
The core team keeps track of companies and maintainers who are considered as trustworthy OP-TEE users. This is a
vetted list and people from companies can only be added to that list after first talking to the core team. In short what
is required to be added to that list is:
• A justification of why you need to know about security issues and should have security fixes before they are
going public.
• A company email address (we do not accept gmail, yahoo and similar addresses).
• You accept our disclosure policy rules (as described at here).
Note: The core team and Linaro have the rights to deny anyone to be on this list. We also have the rights to remove
people on the list if there should be a reason to do so.
By default we are following the industry standard with 90-days disclosure deadline. This applies both when we find
security issues that needs to be fixed in the upstream project, as well as when we are the ones reporting issues found in
vendor trees (forks of OP-TEE). The reason for 90-days is to give companies enough time to patch and deploy updated
software to their devices.
Likewise we are going to propose a 90-days disclosure deadline for issues that are being reported to us, that we are
supposed to fix.
However, for issues that falls in the severity category ‘low’ and in some cases ‘medium’ (see Severity table below),
we have the rights to decide whether to upstream patches as soon as they are ready. If the reporter or the some of the
trustworthy stakeholders knowing about the security issue disagrees, then they must inform the core team and Linaro
about it as soon as possible and then we will come up with an alternate plan.
0day exploits
This is a previously unknown and unpatched vulnerability which is been used actively in the wild. As a consequence
of that we believe that 0day exploits require a much more urgent action. I.e., a fix or some kind of mitigation that
limits the damage needs to be created as soon as possible. Our target for such fixes and mitigations are within 14 days
from the day when we learned about the 0day exploit (full weeks, including weekends).
For regular security issues (non 0day) we follow the flow chart below. Note that the orange path is when it is a low
(and maybe medium) severity issue we are dealing with, so that is a special case with an alternate path.
Issue reported
Day 1
90 day counter starts
Create mitigations
Patch ready
Yes No
Update CVE
Create CVE Upstream Fixes Update security advisories Inform stakeholders
(if created)
Create mitigations
Yes
1.5.5 Recognition
Once the disclosure deadline has passed and information and mitigations will go public we want to give credits to the
ones finding, reporting and fixing the issues. Typically that is given in two ways. One is in textual form at our security
advisories page and the other way is directly in patches applied on the upstream project in questions.
For patches we prefer having a real physical person being mentioned (see Reported-by and Suggested-by in the example
below), but also a company name or group could be used if it was a joint effort finding the security issue or if the person
finding the issue prefer not being mentioned directly for some reason. A patch would typically look like this:
Fixes CVE-20xx-YYYY
1.5.6 CVE
If there is a need to request a CVE identifier, then the Distributed Weakness Filing Project should be used. At that
page you will find the current link to the DWF project.
When deciding the severity for a vulnerability we start out by doing a scoring similar to the DREAD scoring system,
but tweaked for OP-TEE purposes. This mainly serves as a guide to get some kind of indication of the severity. The
final severity is decided on case by case basis.
Note: A DREAD score can change over time. The initial analysis could give a certain score, but later on when a
vulnerability is well known and exploits are readily available the score will be different (ususally more severe).
Damage Potential
This should give an answer to much damage is caused if the vulnerability is exploited.
ScoreDamange potential
0 No damage.
1 Normal World User space is compromised and could leak sensitive data.
1 Denial of service from Normal World.
2 Normal World Linux kernel space is compromised and could leak sensitive data.
5 TEE Trusted Application compromised and could leak data only accessible by the Trusted Application.
7 TEE core (kernel space) compromised and leaking trivial information.
9 TEE core (kernel space) compromised and leaking sensitive information.
10 TEE fully compromised and the attacker in full control.
Reproducibility
This describes how easy (or hard) it is to reproduce the attack.
ScoreReproducibility
0 Not reproducible.
1 No proven attack exists.
1 The attack is very difficult to reproduce, even with knowledge of the security hole (requires special lab
equipment for example)
2 Proof of concept attack exists, but only works in a specially crafted, non-standard configuration.
4 The attack can be reproduced, but only with tooling / software / knowledge that has not been made public
(typically the one finding the security issue have created a tool, which hasn’t been released yet).
9 The attack can be reproduced, but only with tooling (JTAG, ChipWhisperer etc) / software / knowledge that
is readily available to anyone.
10 The attack can be reproduced every time by a novice user without any need for extra tools.
Exploitability
This should answer how easy it is to launch an attack.
ScoreExploitability
0 Not exploitable.
1 Theoretically exploitable (even with knowledge, there seems to be no viable path for a real exploit).
7 Only authenticated user(s) can make the attack.
8 A skilled programmer with in-depth knowledge could make the attack.
9 A novice programmer could make the attack in a short time.
10 A novice user could make the attack in a short time (exploits readily available on internet and/or integrated in
known hacker/pen-testing tools).
Affected Users
This should give a rough answer to how many people are affected by a successful attack.
ScoreAffected Users
0 No users affected.
1 All users, running a debug/developer configuration.
1 A single user.
10 All users, running a release configuration (key customers).
Discoverability
This should answer how easy it is to discover the threat.
ScoreDiscoverability
0 Not discoverable.
1 The vulnerability would require other successful exploits in order to be able to discover this bug.
2 The bug is obscure, and it is unlikely that users will work out damage potential.
5 Information explaining the attack exists, but is only shared with a small group of people (and it is not intended
to be shared publicly in a foreseeable time or until mitigations has been merged).
10 Published information explains the attack.
Severity table
Based on the DREAD score, we get some kind of indication of the severity. In the table below you can see how we
are mapping things between a DREAD score and severity.
Severity
Score CVE? Comment
No [0, 1) No This is not considered as a security issue, it’s a regular bug.
risk CVE
cre-
ated.
Low[1, 4) No This could be seen as a security issue, but could probably be treated as general bug.
CVE
cre-
ated.
Medium
[4, 7) Depends.
This is a security issue, but on the lower side of the score it might be treated as a bug.
For the higher end it is likely that a CVE will be created.
High[7, 9) CVEIt is definitely a security issue.
cre-
ated.
Critical
[9, 10] CVEIt is definitely a security issue, very urgent to start working with mitigations etc.
cre-
ated.
Example
To have a better understanding how this would look like in practice, let’s show a couple of examples.
Example 1 - Spectre v2 - Branch Target Injection (CVE-2017-5715)
Note that this example should be seed from a TrustZone / TEE point of view.
• D: What damage could it cause?
– TEE leaking sensitive data, i.e., 9.
• R: Easy to reproduce?
– No proven attack exists on TrustZone/TEE software, i.e, 1.
• E: Easy to launch the attack?
– Theoretically exploitable, i.e., 1
• A: How many users would be affected by a successful attack?
– All users, i.e., 10.
• D: How easy is it to discover this issue?
– It’s public information, i.e., 10.
This gives the score: (9 + 1 + 1 + 10 + 10) / 5 = 6.2 which indicates that this would a bit on the higher end of medium
severity.
Example 2 - Bellcore attack on OP-TEE (CVE-2017-1000412)
• D: What damage could it cause?
– TEE leaking sensitive data (private key used to sign and verify Trusted Applications), i.e., 9.
• R: Easy to reproduce?
– With a ChipWhisperer (readily available) it would be possible for a somewhat skilled engineer to do
this on their own on a device running OP-TEE, i.e., 9.
• E: Easy to launch the attack?
– A skilled engineer with in-depth knowledge could make the attack, i.e., 8.
• A: How many users would be affected by a successful attack?
– All users, i.e., 10.
• D: How easy is it to discover this issue?
– It’s public information, i.e., 10.
This gives the score: (9 + 9 + 8 + 10 + 10) / 5 = 9.2 which indicates that this would be a critical issue.
This document defines the format of the copyright and license headers in OP-TEE source files. Such headers shall
comply with the rules described here, which are compatible with the rules adopted by the Linux kernel community.
• (Rule 1.1) Shall contain exactly one SPDX license identifier, which can express a single or multiple licenses
(refer to SPDX for syntax details)
• (Rule 1.2) The SPDX license identifier shall be added as a comment line. It shall be the first possible line in the
file which can contain a comment. The comment style shall depend on the file type:
• (Rule 1.2.1) C source: // SPDX-License-Identifier: <expression>
• (Rule 1.2.2) C header: /* SPDX-License-Identifier: <expression> */
• (Rule 1.2.3) Assembly: /* SPDX-License-Identifier: <expression> */
• (Rule 1.2.4) Python, shell: # SPDX-License-Identifier: <expression>
• (Rule 1.3) Shall contain at least one copyright line
• (Rule 1.4) Shall not contain the mention ‘All rights reserved’
• (Rule 1.5) Shall not contain any license notice other than the SPDX license identifier
Note that files imported from external projects are not new files. The rules for pre-existing files (below) apply.
• (Rule 2.1) SPDX license identifiers shall be added according to the license notice(s) in the file and the rules
above (1.1 and 1.2*)
• (Rule 2.2) It is recommended that license notices be removed once the corresponding identifier has been added.
Note however that this may only be done by the copyright holder(s) of the file.
• (Rule 2.3) Similar to 2.2, and subject to the same conditions, the text: “All rights reserved” shall be removed
also.
Several platforms are supported. In order to manage slight differences between platforms, a PLATFORM_FLAVOR
flag has been introduced. The PLATFORM and PLATFORM_FLAVOR flags define the whole configuration for a
chip the where the Trusted OS runs. Note that there is also a composite form which makes it possible to append
PLATFORM_FLAVOR directly, by adding a dash in-between the names. The composite form is shown below for the
different boards. For more specific details about build flags etc, please read Configuration and flags. Some platforms
have different sub-maintainers, please refer to the file MAINTAINERS for contact details for various platforms.
1.8 Presentations
Below are presentations coming from engineers working with OP-TEE in one or another way. Note that the older they
are, the less relevant is the information in them. So do not trust blindly what was said back in the days, cross check
with latest version to understand whether things have changed or not.
The links are sorted in chronological order, newest first and oldest at the end.
• YVR18
– YVR18-414 - Keymaster and Gatekeeper (slides, video)
– YVR18-117 - SWG updates since HKG18 (slides, video)
• HKG18
– HKG18-402 - Build secure key management services in OP-TEE (slides, video)
• SFO17
– SFO17-309 - Secure storage updates (slides, video)
• Webinar
– TEE Linux kernel support and open source security (slides, video)
• BUD17
– BUD17-416 - Benchmark and profiling in OP TEE (slides, video)
– BUD17-400 - Secure Data Path with OPTEE (slides, video)
• LAS16
– LAS16-504 - Secure Storage updates in OP-TEE (slides, video)
– LAS16-406 - Android Widevine on OP-TEE (slides, video)
– LAS16-111 - Easing Access to ARM TrustZone OP TEE and Raspberry Pi 3 (slides, video)
• BKK16
– BKK16-201 - PlayReady OP-TEE Integration with Secure Video Path (slides, video)
– BKK16-110 - A Gentle Introduction to Trusted Execution and OP-TEE (slides)
• SFO15
– SFO15-503 - Secure storage in OP-TEE (slides, video)
– SFO15-205 - OP-TEE Content Decryption with Microsoft PlayReady on ARM TrustZone (slides,
video)
– SFO15-200 - TEE kernel driver (slides, video)
• HKG15
– HKG15-311 - OP-TEE for Beginners and Porting Review (slides, video)
– HKG15-307 - OP-TEE pager (slides, video)
• LCU14
– LCU14-306 - OP-TEE Future Enhancements (slides)
– LCU14-302 - How to port OP-TEE to another platform (slides, video)
– LCU14-107 - OP-TEE on ARMv8-A (slides, video)
1.8. Presentations 17
OP-TEE Documentation
– LCU14-103 - How to create and run Trusted Applications on OP-TEE (slides, video)
• LCA14
– LCA14-502 - The way to a generic TrustZone solution (slides)
– LCA14-418 - Testing a secure framework (slides)
Architecture documentation
2.1 Core
This document describes how optee_os handles switches of world execution context based on SMC exceptions and in-
terrupt notifications Interrupt notifications are IRQ/FIQ exceptions which may also imply switching of world execution
context: normal world to secure world, or secure world to normal world.
This section lists all the cases where optee_os is involved in world context switches. Optee_os executes in the secure
world. World switch is done by the cores secure monitor level/mode, referred below as the Monitor.
When the normal world invokes the secure world, the normal world executes a SMC instruction. The SMC exception
is always trapped by the Monitor. If the related service targets the trusted OS, the Monitor will switch to optee_os
world execution. When the secure world returns to the normal world, optee_os executes a SMC that is caught by the
Monitor which switches back to the normal world.
When a secure interrupt is signaled by the Arm GIC, it shall reach the optee_os interrupt exception vector. If the
secure world is executing, optee_os will handle straight the interrupt from its exception vector. If the normal world is
executing when the secure interrupt raises, the Monitor vector must handle the exception and invoke the optee_os to
serve the interrupt.
When a non-secure interrupt is signaled by the Arm GIC, it shall reach the normal world interrupt exception vector.
If the normal world is executing, it will handle straight the exception from its exception vector. If the secure world is
executing when the non-secure interrupt raises, optee_os will temporarily return back to normal world via the Monitor
to let normal world serve the interrupt.
19
OP-TEE Documentation
Monitor vector is VBAR_EL3 in AArch64 and MVBAR in Armv7-A/AArch32. Monitor can be reached while normal
world or secure world is executing. The executing secure state is known to the Monitor through the SCR_NS.
Monitor can be reached from a SMC exception, an IRQ or FIQ exception (so-called interrupts) and from asynchronous
aborts. Obviously monitor aborts (data, prefetch, undef) are local to the Monitor execution.
The Monitor can be external to optee_os (case CFG_WITH_ARM_TRUSTED_FW=y). If not, provides a local secure
monitor core/arch/arm/sm. Armv7-A platforms should use the optee_os secure monitor. Armv8-A platforms
are likely to rely on an Trusted Firmware A.
When executing outside the Monitor, the system is executing either in the normal world (SCR_NS=1) or in the secure
world (SCR_NS=0). Each world owns its own exception vector table (state vector):
• VBAR_EL2 or VBAR_EL1 non-secure or VBAR_EL1 secure for AArch64.
• HVBAR or VBAR non-secure or VBAR secure for Armv7-A and AArch32.
All SMC exceptions are trapped in the Monitor vector. IRQ/FIQ exceptions can be trapped either in the Monitor vector
or in the state vector of the executing world.
When the normal world is executing, the system is configured to route:
• secure interrupts to the Monitor that will forward to optee_os
• non-secure interrupts to the executing world exception vector.
When the secure world is executing, the system is configured to route:
• secure and non-secure interrupts to the executing optee_os exception vector. optee_os shall forward the non-
secure interrupts to the normal world.
Optee_os non-secure interrupts are always trapped in the state vector of the executing world. This is reflected by a
static value of SCR_(IRQ|FIQ).
Some general purpose registers are not saved and restored on entry and exit, those are used to pass parameters between
secure and normal world (see ARM_DEN0028A_SMC_Calling_Convention for details).
Entry and exit of Trusted OS
On entry and exit of Trusted OS each CPU is uses a separate entry stack and runs with IRQ and FIQ blocked. SMCs
are categorised in two flavor: fast and standard.
• For fast SMCs, optee_os will execute on the entry stack with IRQ/FIQ blocked until the execution returns to
normal world.
• For standard SMCs, optee_os will at some point execute the requested service with interrupts unblocked.
In order to handle interrupts, mainly forwarding of foreign interrupts, optee_os assigns a trusted thread
(core/arch/arm/kernel/thread.c) to the SMC request. The trusted thread stores the execution context of the re-
quested service. This context can be suspended and resumed as the requested service executes and is interrupted.
The trusted thread is released only once the service execution returns with a completion status.
For standard SMCs, optee_os allocates or resumes a trusted thread then unblock the IRQ/FIQ lines. When the
optee_os needs to invoke the normal world from a foreign interrupt or a remote service call, optee_os blocks
IRQ/FIQ and suspends the trusted thread. When suspending, optee_os gets back to the entry stack.
• Both fast and standard SMC end on the entry stack with IRQ/FIQ blocked and optee_os invokes the Monitor
through a SMC to return to the normal world.
This section uses the Arm GICv1/v2 conventions: IRQ signals non-secure interrupts while FIQ signals secure inter-
rupts. On a GICv3 configuration, one should exchange IRQ and FIQ in this section.
Forward a Foreign Interrupt from Secure World to Normal World
When an IRQ is received in secure world as an IRQ exception then secure world:
2.1. Core 21
OP-TEE Documentation
1. Saves trusted thread context (entire state of all processor modes for Armv7-A)
2. Blocks (masks) all interrupts (IRQ and FIQ)
3. Switches to entry stack
4. Issues an SMC with a value to indicates to normal world that an IRQ has been delivered and last SMC call
should be continued
The monitor restores normal world context with a return code indicating that an IRQ is about to be delivered. Normal
world issues a new SMC indicating that it should continue last SMC.
The monitor restores secure world context which locates the previously saved context and checks that it is a return
from IRQ that is requested before restoring the context and lets the secure world IRQ handler return from exception
where the execution would be resumed.
Note that the monitor itself does not know/care that it has just forwarded an IRQ to normal world. The bookkeeping is
done in the trusted thread handling in Trusted OS. Normal world is responsible to decide when the secure world thread
should resume execution. See some details in section [Trusted Thread Scheduling](#5-trusted-thread-scheduling).
This section uses the Arm GICv1/v2 conventions: FIQ signals secure interrupts while IRQ signals non-secure inter-
rupts. On a GICv3 configuration, one should exchange IRQ and FIQ in this section. A FIQ can be received during
two different states, either in normal world (SCR_NS is set) or in secure world (SCR_NS is cleared). When the secure
monitor is active (Armv8-A EL3 or Armv7-A Monitor mode) FIQ is masked. FIQ reception in the two different states
is described below.
Deliver FIQ to secure world when SCR_NS is set
When the monitor gets an FIQ exception it:
1. Saves normal world context and restores secure world context from last secure world exit (which will have IRQ
and FIQ blocked)
2. Clears SCR_FIQ when clearing SCR_NS
3. Sets “FIQ” as parameter to secure world entry
4. Does a return from exception into secure context
5. Secure world unmasks FIQs because of the “FIQ” parameter
6. FIQ is received as in exception using the state vector
7. The state vector handle returns from exception in secure world
8. Secure world issues an SMC to return to normal world
9. Monitor saves secure world context and restores normal world context
10. Does a return from exception into restored context
2.1. Core 23
OP-TEE Documentation
Fig. 4: FIQ received while processing an IRQ forwarded from secure world
A memory object, MOBJ, describes a piece of memory. The interface provided is mostly abstract when it comes to
using the MOBJ to populate translation tables etc. There are different kinds of MOBJs describing:
• Physically contiguous memory
– created with mobj_phys_alloc().
• Virtual memory
– one instance with the name mobj_virt available.
– spans the entire virtual address space.
• Physically contiguous memory allocated from a tee_mm_pool_t *
– created with mobj_mm_alloc().
2.1. Core 25
OP-TEE Documentation
• Paged memory
– created with mobj_paged_alloc().
– only contains the supplied size and makes mobj_is_paged() return true if supplied as argument.
• Secure copy paged shared memory
– created with mobj_seccpy_shm_alloc().
– makes mobj_is_paged() and mobj_is_secure() return true if supplied as argument.
2.1.3 MMU
Translation tables
OP-TEE uses several L1 translation tables, one large spanning 4 GiB and two or more small tables spanning 32 MiB.
The large translation table handles kernel mode mapping and matches all addresses not covered by the small translation
tables. The small translation tables are assigned per thread and covers the mapping of the virtual memory space for
one TA context.
Memory space between small and large translation table is configured by TTBRC. TTBR1 always points to the large
translation table. TTBR0 points to the a small translation table when user mapping is active and to the large translation
table when no user mapping is currently active. For details about registers etc, please refer to a Technical Reference
Manual for your architecture, for example Cortex-A53 TRM.
The translation tables has certain alignment constraints, the alignment (of the physical address) has to be the same as
the size of the translation table. The translation tables are statically allocated to avoid fragmentation of memory due
to the alignment constraints.
Each thread has one small L1 translation table of its own. Each TA context has a compact representation of its L1
translation table. The compact representation is used to initialize the thread specific L1 translation table when the TA
context is activated.
Small L1
Spans 32 MiB
per entry
Thread 0 ctx active
0
When switching to normal world either via a foreign interrupt or RPC there is a chance that secure world will resume
execution on a different CPU. This means that the new CPU need to be configured with the context of the currently
active TA. This is solved by always setting the TA context in the CPU when resuming execution. Here is room for
improvements since it is more likely than not that it is the same CPU that resumes execution in secure world.
2.1.4 Pager
OP-TEE currently requires ~256 KiB RAM for OP-TEE kernel memory. This is not a problem if OP-TEE uses
TrustZone protected DDR, but for security reasons OP-TEE may need to use TrustZone protected SRAM instead. The
amount of available SRAM varies between platforms, from just a few KiB up to over 512 KiB. Platforms with just a
few KiB of SRAM cannot be expected to be able to run a complete TEE solution in SRAM. But those with 128 to
2.1. Core 27
OP-TEE Documentation
256 KiB of SRAM can be expected to have a capable TEE solution in SRAM. The pager provides a solution to this by
demand paging parts of OP-TEE using virtual memory.
Secure memory
TrustZone protected SRAM is generally considered more secure than TrustZone protected DRAM as there is usually
more attack vectors on DRAM. The attack vectors are hardware dependent and can be different for different platforms.
Backing store
TrustZone protected DRAM or in some cases non-secure DRAM is used as backing store. The data in the backing
store is integrity protected with one hash (SHA-256) per page (4KiB). Readonly pages are not encrypted since the
OP-TEE binary itself is not encrypted.
Partitioning of memory
The code that handles demand paging must always be available as it would otherwise lead to deadlock. The virtual
memory is partitioned as:
Type Sections
+--------------+-----------------+
| | text |
| | rodata |
| | data |
| unpaged | bss |
| | heap1 |
| | nozi |
| | heap2 |
+--------------+-----------------+
| init / paged | text_init |
| | rodata_init |
+------------- +-----------------+
| paged | text_pageable |
| | rodata_pageable |
+--------------+-----------------+
| demand alloc | |
| | |
+--------------+-----------------+
Where nozi stands for “not zero initialized”, this section contains entry stacks (thread stack when TEE pager is not
enabled) and translation tables (TEE pager cached translation table when the pager is enabled and LPAE MMU is
used).
The init area is available when OP-TEE is initializing and contains everything that is needed to initialize the pager.
After the pager has been initialized this area will be used for demand paged instead.
The demand alloc area is a special area where the pages are allocated and removed from the pager on demand.
Those pages are returned when OP-TEE does not need them any longer. The thread stacks currently belongs this area.
This means that when a stack is not used the physical pages can be used by the pager for better performance.
The technique to gather code in the different area is based on compiling all functions and data into separate sections.
The unpaged text and rodata is then gathered by linking all object files with --gc-sections to eliminate sections
that are outside the dependency graph of the entry functions for unpaged functions. A script analyzes this ELF file and
generates the bits of the final link script. The process is repeated for init text and rodata. What is not “unpaged” or
“init” becomes “paged”.
+----------+
| Header |
+----------+
| Init |
+----------+
| Hashes |
+----------+
| Pageable |
+----------+
struct optee_header {
uint32_t magic;
uint8_t version;
uint8_t arch;
uint16_t flags;
uint32_t init_size;
uint32_t init_load_addr_hi;
uint32_t init_load_addr_lo;
uint32_t init_mem_usage;
uint32_t paged_size;
};
The header is only used by the loader of OP-TEE, not OP-TEE itself. To initialize OP-TEE the loader loads
the complete binary into memory and copies what follows the header and the following init_size bytes to
(init_load_addr_hi << 32 | init_load_addr_lo). init_mem_usage is used by the loader to be
able to check that there is enough physical memory available for OP-TEE to be able to initialize at all. The loader
supplies in r0/x0 the address of the first byte following what was not copied and jumps to the load address to start
OP-TEE.
In addition to overall binary with partitions inside described as above, extra three binaries are generated simultaneously
during build process for loaders who support loading separate binaries:
+----------+
| Header |
+----------+
+----------+
| Init |
+----------+
| Hashes |
+----------+
+----------+
| Pageable |
+----------+
In this case, loaders load header binary first to get image list and information of each image; and then load each of
2.1. Core 29
OP-TEE Documentation
them into specific load address assigned in structure. These binaries are named with v2 suffix to distinguish from the
existing binaries. Header format is updated to help loaders loading binaries efficiently:
#define OPTEE_IMAGE_ID_PAGER 0
#define OPTEE_IMAGE_ID_PAGED 1
struct optee_image {
uint32_t load_addr_hi;
uint32_t load_addr_lo;
uint32_t image_id;
uint32_t size;
};
struct optee_header_v2 {
uint32_t magic;
uint8_t version;
uint8_t arch;
uint16_t flags;
uint32_t nb_images;
struct optee_image optee_image[];
};
Magic number and architecture are identical as original. Version is increased to two. load_addr_hi and
load_addr_lo may be 0xFFFFFFFF for pageable binary since pageable part may get loaded by loader into
dynamic available position. image_id indicates how loader handles current binary. Loaders who don’t support
separate loading just ignore all v2 binaries.
The pager is initialized as early as possible during boot in order to minimize the “init” area. The global variable
tee_mm_vcore describes the virtual memory range that is covered by the level 2 translation table supplied to
tee_pager_init().
Assign pageable areas
A virtual memory range to be handled by the pager is registered with a call to tee_pager_add_core_area().
which takes a pointer to tee_mm_entry_t to tell the range, flags to tell how memory should be mapped (readonly,
execute etc), and pointers to backing store and hashes of the pages.
Assign physical pages
Physical SRAM pages are supplied by calling tee_pager_add_pages()
tee_pager_add_pages() takes the physical address stored in the entry mapping the virtual address “vaddr” and
“npages” entries after that and uses it to map new pages when needed. The unmap parameter tells whether the pages
should be unmapped immediately since they does not contain initialized data or be kept mapped until they need to be
recycled. The pages in the “init” area are supplied with unmap == false since those page have valid content and
are in use.
Invocation
The pager is invoked as part of the abort handler. A pool of physical pages are used to map different virtual addresses.
When a new virtual address needs to be mapped a free physical page is mapped at the new address, if a free physical
page cannot be found the oldest physical page is selected instead. When the page is mapped new data is copied from
backing store and the hash of the page is verified. If it is OK the pager returns from the exception to resume the
execution.
Paging of user TA
Paging of user TAs can optionally be enabled with CFG_PAGED_USER_TA=y. Paging of user TAs is analogous to
paging of OP-TEE kernel parts but with a few differences:
• Read/write pages are paged in addition to read-only pages
• Page tables are managed dynamically
tee_pager_add_uta_area() is used to setup initial read/write mapping needed when populating the TA. When
the TA is fully populated and relocated tee_pager_set_uta_area_attr() changes the mapping of the area
to strict permissions used when the TA is running.
2.1.5 Stacks
Different stacks are used during different stages. The stacks are:
• Secure monitor stack (128 bytes), bound to the CPU. Only available if OP-TEE is compiled with a secure
monitor always the case if the target is Armv7-A but never for Armv8-A.
• Temp stack (small ~1KB), bound to the CPU. Used when transitioning from one state to another. Interrupts are
always disabled when using this stack, aborts are fatal when using the temp stack.
• Abort stack (medium ~2KB), bound to the CPU. Used when trapping a data or pre-fetch abort. Aborts from
user space are never fatal the TA is only killed. Aborts from kernel mode are used by the pager to do the demand
paging, if pager is disabled all kernel mode aborts are fatal.
• Thread stack (large ~8KB), not bound to the CPU instead used by the current thread/task. Interrupts are usually
enabled when using this stack.
Notes for Armv7-A/AArch32:
2.1. Core 31
OP-TEE Documentation
Boot
During early boot the CPU is configured with the temp stack which is used until OP-TEE exits to normal world the
first time.
Notes for AArch64:
SPSEL is always 0 on entry/exit to have SP_EL0 acting as stack pointer.
Normal entry
Each time OP-TEE is entered from normal world the temp stack is used as the initial stack. For fast calls this is the
only stack used. For normal calls an empty thread slot is selected and the CPU switches to that stack.
Normal exit
Normal exit occurs when a thread has finished its task and the thread is freed. When the main thread function,
tee_entry_std(), returns interrupts are disabled and the CPU switches to the temp stack instead. The thread is
freed and OP-TEE exits to normal world.
RPC exit
RPC exit occurs when OP-TEE need some service from normal world. RPC can currently only be performed with a
thread is in running state. RPC is initiated with a call to thread_rpc() which saves the state in a way that when
the thread is restored it will continue at the next instruction as if this function did a normal return. CPU switches to
use the temp stack before returning to normal world.
Foreign interrupt exit occurs when OP-TEE receives a foreign interrupt. For Arm GICv2 mode, foreign interrupt
is sent as IRQ which is always handled in normal world. Foreign interrupt exit is similar to RPC exit but it is
thread_irq_handler() and elx_irq() (respectively for Armv7-A/Aarch32 and for Aarch64) that saves the
thread state instead. The thread is resumed in the same way though. For Arm GICv3 mode, foreign interrupt is sent as
FIQ which could be handled by either secure world (EL3 in AArch64) or normal world. This mode is not supported
yet.
Notes for Armv7-A/AArch32:
SP_IRQ is initialized to temp stack instead of a separate stack. Prior to exiting to normal world CPU state is changed
to SVC and temp stack is selected.
Notes for AArch64:
SP_EL0 is assigned temp stack and is selected during IRQ processing. The original SP_EL0 is saved in the thread
context to be restored when resuming.
Resume entry
OP-TEE is entered using the temp stack in the same way as for normal entry. The thread to resume is looked up and
the state is restored to resume execution. The procedure to resume from an RPC exit or an foreign interrupt exit is
exactly the same.
Syscall
Shared Memory is a block of memory that is shared between the non-secure and the secure world. It is used to transfer
data between both worlds.
The shared memory is allocated by the Linux driver from a pool struct shm_pool, the pool contains:
• The physical address of the start of the pool
• The size of the pool
• Whether or not the memory is cached
• List of chunk of memory allocated.
Note:
• The shared memory pool is physically contiguous.
• The shared memory area is not secure as it is used by both non-secure and secure world.
It is the Linux kernel driver for OP-TEE that is responsible for initializing the shared memory pool, given information
provided by the OP-TEE core. The Linux driver issues a SMC call OPTEE_SMC_GET_SHM_CONFIG to retrieve the
information
• Physical address of the start of the pool
• Size of the pool
• Whether or not the memory is cached
The shared memory pool configuration is platform specific. The memory mapping, including the area
MEM_AREA_NSEC_SHM (shared memory with non-secure world), is retrieved by calling the platform-specific func-
tion bootcfg_get_memory(). Please refer to this function and the area type MEM_AREA_NSEC_SHM to see the
configuration for the platform of interest. The Linux driver will then initialize the shared memory pool accordingly.
2.1. Core 33
OP-TEE Documentation
It is the Linux kernel driver for OP-TEE that is responsible for allocating chunks of shared memory. OP-TEE
linux kernel driver relies on linux kernel generic allocation support (CONFIG_GENERIC_ALLOCATION) to allo-
cation/release of shared memory physical chunks. OP-TEE linux kernel driver relies on linux kernel dma-buf support
(CONFIG_DMA_SHARED_BUFFER) to track shared memory buffers references.
2.1.7 SMC
SMC Interface
OP-TEE’s SMC interface is defined in two levels using optee_smc.h and optee_msg.h. The former file defines SMC
identifiers and what is passed in the registers for each SMC. The latter file defines the OP-TEE Message protocol
which is not restricted to only SMC even if that currently is the only option available.
SMC communication
The main structure used for the SMC communication is defined in struct optee_msg_arg (in optee_msg.h). If
we are looking into the source code, we could see that communication mainly is achieved using optee_msg_arg
and thread_smc_args (in thread.h), where optee_msg_arg could be seen as the main structure. What will
happen is that the Linux kernel TEE driver will get the parameters either from optee_client or directly from an internal
service in Linux kernel. The TEE driver will populate the struct optee_msg_arg with the parameters plus some
additional bookkeeping information. Parameters for the SMC are passed in registers 1 to 7, register 0 holds the SMC
id which among other things tells whether it is a standard or a fast call.
OP-TEE core uses a couple of threads to be able to support running jobs in parallel (not fully enabled!). There are
handlers for different purposes. In thread.c you will find a function called thread_init_primary which assigns
init_handlers (functions) that should be called when OP-TEE core receives standard or fast calls, FIQ and PSCI
calls. There are default handlers for these services, but the platform can decide if they want to implement their own
platform specific handlers instead.
Synchronization primitives
OP-TEE has three primitives for synchronization of threads and CPUs: spin-lock, mutex, and condvar.
Spin-lock
A spin-lock is represented as an unsigned int. This is the most primitive lock. Interrupts should be disabled
before attempting to take a spin-lock and should remain disabled until the lock is released. A spin-lock is initialized
with SPINLOCK_UNLOCK.
Mutex
A mutex is represented by struct mutex. A mutex can be locked and unlocked with interrupts enabled or disabled,
but only from a normal thread. A mutex cannot be used in an interrupt handler, abort handler or before a thread has
been selected for the CPU. A mutex is initialized with either MUTEX_INITIALIZER or mutex_init().
2.1. Core 35
OP-TEE Documentation
When a mutex is locked it is owned by the thread calling mutex_lock() or mutex_trylock(), the mutex may
only be unlocked by the thread owning the mutex. A thread should not exit to TA user space when holding a mutex.
Condvar A condvar is represented by struct condvar. A condvar is similar to a pthread_condvar_t in the
pthreads standard, only less advanced. Condition variables are used to wait for some condition to be fulfilled
and are always used together a mutex. Once a condition variable has been used together with a certain mutex, it
must only be used with that mutex until destroyed. A condvar is initialized with CONDVAR_INITIALIZER or
condvar_init().
The caller of condvar_signal() or condvar_broadcast() should hold the mutex associated with the con-
dition variable to guarantee that a waiter does not miss the signal.
This document describes how the TEE Cryptographic Operations API is implemented, how the default crypto provider
may be configured at compile time, and how it may be replaced by another implementation.
2.2.1 Overview
There are several layers from the Trusted Application to the actual crypto algorithms. Most of the crypto code runs in
kernel mode inside the TEE core.
Here is a schematic view of a typical call to the crypto API. The numbers in square brackets ([1], [2]. . . ) refer to the
sections below.
OP-TEE implements the Cryptographic Operations API defined by the GlobalPlatform association in the TEE In-
ternal Core API. This includes cryptographic functions that span various cryptographic needs: message digests,
symmetric ciphers, message authentication codes (MAC), authenticated encryption, asymmetric operations (encryp-
tion/decryption or signing/verifying), key derivation, and random data generation. These functions make up the TEE
Cryptographic Operations API.
The Internal API is implemented in tee_api_operations.c, which is compiled into a static library: ${O}/
ta_arm{32,64}-lib/libutee/libutee.a.
Most API functions perform some parameter checking and manipulations, then invoke some utee_* function to switch
to kernel mode and perform the low-level work.
The utee_* functions are declared in utee_syscalls.h and implemented in utee_syscalls_asm.S They are simple system
call wrappers which use the SVC instruction to switch to the appropriate system service in the OP-TEE kernel.
All cryptography-related system calls are declared in tee_svc_cryp.h and implemented in tee_svc_cryp.c. In addition
to dealing with the usual work required at the user/kernel interface (checking parameters and copying memory buffers
between user and kernel space), the system calls invoke a private abstraction layer: the Crypto API, which is declared
in crypto.h. It serves two main purposes:
1. Allow for alternative implementations, such as hardware-accelerated versions.
2. Provide an easy way to disable some families of algorithms at compile-time to save space. See LibTomCrypt
below.
The crypto_*() functions implement the actual algorithms and helper functions. TEE Core has one global active
implementation of this interface. The default implementation, mostly based on LibTomCrypt, is as follows:
#if !defined(_CFG_CRYPTO_WITH_HASH)
TEE_Result crypto_hash_get_ctx_size(uint32_t algo __unused,
size_t *size __unused)
{
return TEE_ERROR_NOT_IMPLEMENTED;
}
...
#endif /*_CFG_CRYPTO_WITH_HASH*/
#endif /*_CFG_CRYPTO_WITH_HASH*/
As shown above, families of algorithms can be disabled and crypto.c will provide default null implementations that
will return TEE_ERROR_NOT_IMPLEMENTED.
crypto.h uses implementation-specific types to hold key data for asymmetric algorithms. For instance, here is how a
public RSA key is represented:
This is also how such keys are stored inside the TEE object attributes (TEE_ATTR_RSA_PUBLIC_KEY in this
case). struct bignum is an opaque type, known to the underlying implementation only. struct bignum_ops
provides functions so that the system services can manipulate data of this type. This includes allocation/deallocation,
copy, and conversion to or from the big endian binary format.
Some algorithms may be disabled at compile time if they are not needed, in order to reduce the size of the OP-TEE
image and reduces its memory usage. This is done by setting the appropriate configuration variable. For example:
To add a new implementation, the default one in core/lib/libtomcrypt in combination with what is in core/crypto should
be used as a reference. Here are the main things to consider when adding a new crypto provider:
• Put all the new code in its own directory under core/lib unless it is code that will be used regardless of which
crypto provider is in use. How we are dealing with AES-GCM in core/crypto could serve as an example.
• Avoid modifying tee_svc_cryp.c. It should not be needed.
• Although not all crypto families need to be defined, all are required for compliance to the GlobalPlatform
specification.
• If you intend to make some algorithms optional, please try to re-use the same names for configuration variables
as the default implementation.
2.3.2 /core
2.3.3 /core/arch
2.3.4 /core/arch/arm
2.3.5 /core/arch/arm/include
2.3.6 /core/lib/lib{crypto,sla}
2.3.7 /lib/libutils
2.4.1 Introduction
GlobalPlatform works across industries to identify, develop and publish specifications which facilitate the secure and
interoperable deployment and management of multiple embedded applications on secure chip technology. OP-TEE
has support for GlobalPlatform TEE Client API Specification v1.0 (GPD_SPE_007) and TEE Internal Core API
Specification v1.1.2 (GPD_SPE_010).
The TEE Client API describes and defines how a client running in a rich operating environment (REE) should commu-
nicate with the TEE. To identify a Trusted Application (TA) to be used, the client provides an UUID. All TA’s exposes
one or several functions. Those functions corresponds to a so called commandID which also is sent by the client.
TEE Contexts
The TEE Context is used for creating a logical connection between the client and the TEE. The context must be
initialized before the TEE Session can be created. When the client has completed a jobs running in secure world, it
should finalize the context and thereby also releasing resources.
TEE Sessions
Sessions are used to create logical connections between a client and a specific Trusted Application. When the session
has been established the client have a opened up the communication channel towards the specified Trusted Application
identified by the UUID. At this stage the client and the Trusted Application can start to exchange data.
Below you will find the main functions as defined by GlobalPlatform and which are used in the communication
between the client and the TEE.
TEEC_Result TEEC_InitializeContext(
const char* name,
TEEC_Context* context)
void TEEC_FinalizeContext(
TEEC_Context* context)
TEEC_Result TEEC_OpenSession (
TEEC_Context* context,
TEEC_Session* session,
const TEEC_UUID* destination,
uint32_t connectionMethod,
const void* connectionData,
TEEC_Operation* operation,
uint32_t* returnOrigin)
void TEEC_CloseSession (
TEEC_Session* session)
TEEC_Result TEEC_InvokeCommand(
TEEC_Session* session,
uint32_t commandID,
TEEC_Operation* operation,
uint32_t* returnOrigin)
TEEC_InitializeContext(...)
TEEC_OpenSession(...)
TEEC_InvokeCommand(...)
TEEC_CloseSession(...)
TEEC_FinalizeContext(...)
It is not uncommon that TEEC_InvokeCommand is called several times in row when the session has been estab-
lished.
For a complete example, please see chapter 5.2 Example 1: Using the TEE Client API in the GlobalPlatform TEE
Client API Specification v1.0.
The Internal Core API is the API that is exposed to the Trusted Applications running in the secure world. The TEE
Internal API consists of four major parts:
1. Trusted Storage API for Data and Keys
2. Cryptographic Operations API
3. Time API
4. Arithmetical API
Examples / usage
Calling the Internal Core API is done in the same way as described above using Client API. The best place to find
information how this should be done is in the TEE Internal Core API Specification v1.1.2 which contains many
examples of how to call the various APIs. One can also have a look at the examples in the optee_examples git.
2.4.4 Extensions
In addition to what is stated in TEE Internal Core API, there are some non-official extensions in OP-TEE.
These functions are available to any Trusted Application defined with the flag TA_FLAG_CACHE_MAINTENANCE
sets on. When not set, each function returns the error code TEE_ERROR_NOT_SUPPORTED.
Within these extensions, a Trusted Application is able to operate on the data cache, with the following specification:
Function Description
Write back to memory any dirty data cache lines. The line is marked as not dirty. The valid bit is
TEE_CacheClean()
unchanged.
Purges any valid data cache lines. Any dirty cache lines are first written back to memory, then the
TEE_CacheFlush()
cache line is invalidated.
Invalidate any valid data cache lines. Any dirty line are not written back to memory.
TEE_CacheInvalidate()
Concat KDF
Support for the Concatenation Key Derivation Function (Concat KDF) according to SP 800-56A (Recommendation
for Pair-Wise Key Establishment Schemes Using Discrete Logarithm Cryptography) can be found in OP-TEE.
You may disable this extension by setting the following configuration variable in conf.mk:
CFG_CRYPTO_CONCAT_KDF := n
Implementation notes
All key and parameter sizes must be multiples of 8 bits. That is:
• Input parameters: the shared secret (Z) and OtherInfo.
• Output parameter: the derived key (DerivedKeyingMaterial).
In addition, the maximum size of the derived key is limited by the size of an object of type
TEE_TYPE_GENERIC_SECRET (512 bytes).
This implementation does not enforce any requirement on the content of the OtherInfo parameter. It is the appli-
cation’s responsibility to make sure this parameter is constructed as specified by the NIST specification if compliance
is desired.
API extension
To support Concat KDF, the TEE Internal Core API v1.1 was extended with new algorithm descriptors, new object
types, and new object attributes as described below.
p.95 Add new object type to TEE_PopulateTransientObject
The following entry shall be added to Table 5-8:
Algorithm Identifier
TEE_ALG_CONCAT_KDF_SHA1_DERIVE_KEY 0x800020C1
TEE_ALG_CONCAT_KDF_SHA224_DERIVE_KEY 0x800030C1
TEE_ALG_CONCAT_KDF_SHA256_DERIVE_KEY 0x800040C1
TEE_ALG_CONCAT_KDF_SHA384_DERIVE_KEY 0x800050C1
TEE_ALG_CONCAT_KDF_SHA512_DERIVE_KEY 0x800060C1
p.155 Add new object type for Concat KDF input shared secret
The following entry shall be added to Table 6-10:
Name Value
Pro- Type Comment
tec-
tion
TEE_ATTR_CONCAT_KDF_Z
0xC00001C1
Pro- Ref The shared secret (Z)
tected
TEE_ATTR_CONCAT_KDF_OTHER_INFO
0xD00002C1
Pub- Ref OtherInfo
lic
TEE_ATTR_CONCAT_KDF_DKM_LENGTH
0xF00003C1
Pub- Value The length (in bytes) of the derived keying material to be
lic generated, maximum 512. This is KeyDataLen / 8.
HKDF
OP-TEE implements the HMAC-based Extract-and-Expand Key Derivation Function (HKDF) as specified in RFC
5869. This file documents the extensions to the TEE Internal Core API v1.1 that were implemented to support this
algorithm. Trusted Applications should include <tee_api_defines_extensions.h> to import the definitions.
Note that the implementation follows the recommendations of version 1.1 of the specification for adding new algo-
rithms. It should make it compatible with future changes to the official specification.
You can disable this extension by setting the following in conf.mk:
CFG_CRYPTO_HKDF := n
Algorithm Pos-
sible
Modes
TEE_ALG_HKDF_MD5_DERIVE_KEY TEE_ALG_HKDF_SHA1_DERIVE_KEY TEE_MODE_DERIVE
TEE_ALG_HKDF_SHA224_DERIVE_KEY TEE_ALG_HKDF_SHA256_DERIVE_KEY
TEE_ALG_HKDF_SHA384_DERIVE_KEY TEE_ALG_HKDF_SHA512_DERIVE_KEY
TEE_ALG_HKDF_SHA512_DERIVE_KEY
Algorithm Identifier
TEE_ALG_HKDF_MD5_DERIVE_KEY 0x800010C0
TEE_ALG_HKDF_SHA1_DERIVE_KEY 0x800020C0
TEE_ALG_HKDF_SHA224_DERIVE_KEY 0x800030C0
TEE_ALG_HKDF_SHA256_DERIVE_KEY 0x800040C0
TEE_ALG_HKDF_SHA384_DERIVE_KEY 0x800050C0
TEE_ALG_HKDF_SHA512_DERIVE_KEY 0x800060C0
p.155 Add new object type for HKDF input keying material
The following entry shall be added to Table 6-10:
p.156 Add new operation attributes for HKDF salt and info
The following entries shall be added to Table 6-11:
PBKDF2
This document describes the OP-TEE implementation of the key derivation function, PBKDF2 as specified in RFC
2898 section 5.2. This RFC is a republication of PKCS #5 v2.0 from RSA Laboratories’ Public-Key Cryptography
Standards (PKCS) series.
You may disable this extension by setting the following configuration variable in conf.mk:
CFG_CRYPTO_PBKDF2 := n
API extension
To support PBKDF2, the TEE Internal Core API v1.1 was extended with a new algorithm descriptor, new object types,
and new object attributes as described below.
p.95 Add new object type to TEE_PopulateTransientObject
The following entry shall be added to Table 5-8:
Algorithm Identifier
TEE_ALG_PBKDF2_HMAC_SHA1_DERIVE_KEY 0x800020C2
Note: It’s been a long time since this feature was tested. Most likely things will not work. There are no plans on
updating this at the moment. But if there is anyone out there interested in this willing to spend time on this, it would
of course be appreciated.
A Secure Element (SE) is a tamper-resistant platform (typically a one chip secure microcontroller) capable of
securely hosting applications and their confidential and cryptographic data (e.g. key management) in accordance with
the rules and security requirements set forth by a set of well-identified trusted authorities. Simplified speaking, SE
is a secure platform that can run application (called Applet) on it. In order to communicate with Applet, we need a
transport interface.
SE can be implemented via one of the following technologies
• Embedded SE (accessed via platform dependent interface, unremovable)
• Universal Integrated Circuit Card (UICC, accessed via SIM interface)
• Advanced secure MicroSD (accessed via sdio/mmc interface)
Which means the physical interface between application processor (AP) and SE can be quite different. GlobalPlatform
tries to remove this gap and defined a standard transport API called Secure Element API to cover those different
physical transport layer protocols. SE can be accessed directly in TEE, or indirectly accessed via REE. In later case,
a secure channel is needed to ensure the data stream is not hijacked in REE. (For secure channel, we may leverage
TZC-400 to create a secure memory that is not accessible in REE). To understand SE API, you need to understand the
following terms:
• Trusted Application (TA): An application execute in Trust Execution Environment (TEE), which is the initiator
of SE API.
• Applet: Applications that run on smartcard OS. Secure Element API defines the method to communicate be-
tween host application (in our case, TA) and Applet.
• Service: A service can be used to retrieve all SE readers available in the system, it also provides a service to
create a session from TA to a specific Reader.
• Session: It maintains the connection between TA and a specific Reader. Different TAs can have a session opened
on the same reader. It is SE manager’s responsibility to demux the request from different TAs. Upon a session
is opened by a TA, the card is power-up and ready to accept commands.
• Reader: It is an abstraction to describe the transport interface between the system and SEs. You can imagine
that a SD card slot is a Reader connected with assd. A ril daemon can be another read to talk with UICC cards.
Even embedded SE should have a (virtual) Reader attached to it.
• Logical Channel: It is used by host application (in our case, a TA) to communicate with applets on the smart-
card. [GlobalPlatform Card Specification] defines maximum 20 logical channels, numbered from 0~19. Chan-
nel number 0 is so-called Basic logical channel, or in short, Basic channel. A channel can be
opened or closed by a host application. It is the smartcard OS’s responsibility to manage the state of each logi-
cal channel. Basic channel is always open and cannot be closed. A channel must select an applet, which means
the command passed through the channel will be processed by the selected applet. GlobalPlatform requires a
default applet must be selected on basic channel after system reset. Host application can select different applet
by issuing a SELECT command on basic channel. Other logical channels (numbered 1~19) can be opened
with or without a given Application Identifier (AID). If AID is not given, the applet selected on basic
channel will be selected on the just opened logical channel.
• MultiSelectable or Non-MultiSelectable: An applet can be MultiSelectable or Non-MultiSelectable. For a
Non-MultiSelectable applet, it can only be selected by one channel, further SELECT command on another
channel that is targeting to the applet will fail. MultiSelectable applet can be selected by multiple channels, the
applet can decide maximum number of channels it is willing to accept.
Design
• Manager core/include/tee/se/manager.h: This component manages all Readers on the system. It should provide
reader interface for the Reader developers to register their own Reader instance. (In the case of [JavaCard
Simulator], we should have [PC/SC Passthru Reader] to talk with simulator) It also provides an interface for
client to get reader handle on the system.
• Reader core/include/tee/se/reader.h: It provides the operations that can be applied on a reader handle.
Just like get reader properties and create session to a reader. It’s also responsible for routing an operation(open,
transmit. . . etc) to a specific Reader implementation.
• Protocol (core/include/tee/se/{protocol.h,aid.h,apdu.h}): This module implements the ISO7816 transport layer
protocol that is used to talk with smartcard. It relies on operations provided by Reader to transmit Application
Protocol Data Unit (APDU, refer to ISO7816-4) to a specific SE.
• Session core/include/tee/se/session.h: It provides the operations that can be applied on a session. Just like open
basic or logical channel, and transmit APDU on the session. It relies on protocol layer to create logical, basic
channel and transmit APDU.
• Channel core/include/tee/se/channel.h: It provides the operations that can be applied on a channel. Like trans-
mit an APDU on the channel, select next applet. It relies on protocol module to select AID, and session module
to transport APDU.
• Reader interface core/include/tee/se/reader/interface.h: The abstract layer used to implement a specific Reader
instance, a set of operations need to be implemented to support a new Reader.
– open(): Triggered when the first session is connected, the Reader should be powered on and reset. Doing
initialization. Detect SE is present or not.
– close(): Triggered when the last session to the Reader has been closed. The Reader can be powered
down in this method.
– get_properties(): Get properties of the Reader. Something like the Reader is exclusive to TEE or
not. SE is present. . . etc.
– get_atr(): Get ATR message from the Reader. ATR is defined in ISO7816-3, and it is the message
report by SE to describe the ability of SE.
– transmit(): Transmit an APDU through the Reader which SE attached to.
To test SE API, you need modified QEMU and enhanced JavaCard simulator. Please use this setup script to setup test
environment.
2.5 Libraries
2.5.1 libutee
The TEE Internal Core API describes services that are provided to Trusted Applications. libutee is a library that
implements this API.
libutee is a static library the Trusted Applications shall statically link against. Trusted Applications do execute in
non-privileged secure userspace and libutee also aims at being executed in the non-privileged secure userspace.
Some services for this API are fully statically implemented inside the libutee library while some services for the API
are implemented inside the OP-TEE core (privileged level) and libutee calls such services through system calls.
2.5.2 libmpa
2.6.1 1. Introduction
The first thing you need to do after you have decided to port OP-TEE to another device is to add a new platform device.
That can either be adding a new platform variant (PLATFORM_FLAVOR) if it is a device from a family already
supported, or it can be a brand new platform family (PLATFORM). Typically this initial setup involve configuring
UART, memory addresses etc. For simplicity let us call our fictive platform for “gendev” just so we have something
to refer to when writing examples further down.
2.1 core/arch/arm
In core/arch/arm you will find all the currently supported devices. That is where you are supposed to add a new
platform or modify an existing one. Typically you will find this set of files in a specific platform folder:
$ ls
conf.mk main.c platform_config.h sub.mk
So for the gendev platform it means that the files should be placed in this folder: .. code-block:: bash
core/arch/arm/plat-gendev
conf.mk
This is the device specific makefile where you define configurations unique to your platform. This mainly comprises
two things: - OP-TEE configuration variables (CFG_), which may be assigned values in two ways. CFG_FOO ?=
bar should be used to provide a default value that may be modified at compile time. On the other hand, variables that
must be set to some value and cannot be modified should be set by: $(call force,CFG_FOO,bar). - Compiler
flags for the TEE core, the user mode libraries and the Trusted Applications, which may be added to macros used by
the build system. Please see Platform-specific configuration and flags in the build system documentation.
It is recommended to use a existing platform configuration file as a starting point. For instance, core/arch/arm/plat-
hikey/conf.mk.
The platform conf.mk file should at least define the default platform flavor for the platform, the core configurations
(architecture and number of cores), the main configuration directives (generic boot, arm trusted firmware support,
generic time source, console driver, etc. . . ) and some platform default configuration settings.
PLATFORM_FLAVOR ?= hikey
include core/arch/arm/cpu/cortex-armv8-0.mk
$(call force,CFG_TEE_CORE_NB_CORE,8)
$(call force,CFG_GENERIC_BOOT,y)
$(call force,CFG_PL011,y)
$(call force,CFG_PM_STUBS,y)
$(call force,CFG_SECURE_TIME_SOURCE_CNTPCT,y)
$(call force,CFG_WITH_ARM_TRUSTED_FW,y)
$(call force,CFG_WITH_LPAE,y)
ta-targets = ta_arm32
ta-targets += ta_arm64
CFG_NUM_THREADS ?= 8
CFG_CRYPTO_WITH_CE ?= y
CFG_WITH_STACK_CANARIES ?= y
CFG_CONSOLE_UART ?= 3
CFG_DRAM_SIZE_GB ?= 2
main.c
This platform specific file will contain power management handlers and code related to the UART. We will talk more
about the information related to the handlers further down in this document. For our gendev device it could look like
this (here we are excluding the necessary license header to save some space):
#include <console.h>
#include <drivers/serial8250_uart.h>
#include <kernel/generic_boot.h>
#include <kernel/panic.h>
#include <kernel/pm_stubs.h>
#include <mm/core_mmu.h>
#include <platform_config.h>
#include <stdint.h>
#include <tee/entry_fast.h>
#include <tee/entry_std.h>
/*
* Register the physical memory area for peripherals etc. Here we are
* registering the UART console.
*/
register_phys_mem(MEM_AREA_IO_NSEC, CONSOLE_UART_BASE, SERIAL8250_UART_REG_SIZE);
void console_init(void)
{
serial8250_uart_init(&console_data, CONSOLE_UART_BASE,
CONSOLE_UART_CLK_IN_HZ, CONSOLE_BAUDRATE);
register_serial_console(&console_data.chip);
}
platform_config.h
This is a mandatory header file for every platform, since there are several files relaying upon the existence of this
particular file. This file is where you will find the major differences between different platforms, since this is where
you do the memory configuration, define base addresses etc. we are going to list a few here, but it probably makes
more sense to have a look at the already existing platform_config.h files for the other platforms. Our fictive
gendev could look like this:
#ifndef PLATFORM_CONFIG_H
#define PLATFORM_CONFIG_H
/* 8250 UART */
#define CONSOLE_UART_BASE 0xcafebabe /* UART0 */
#define CONSOLE_BAUDRATE 115200
#define CONSOLE_UART_CLK_IN_HZ 19200000
#endif /* PLATFORM_CONFIG_H */
This is minimal amount of information in the platform_config.h file. I.e, the memory layout for on-chip and
external RAM. Note that parts of the DDR typically will need to be shared with normal world, so there is need for
some kind of memory firewall for this (more about that further down). As you can see we have also added the UART
configuration here, i.e., the DEVICE0_xyz part.
We do encourage everyone to submit their board support to the OP-TEE project itself, so it becomes part of the official
releases and will be maintained by the OP-TEE community itself. If you intend to do so, then there are a few more
things that you are supposed to do.
Update README.md
There is a section (FIXME: update to new Sphinx) that lists all devices officially supported in OP-TEE, that is where
you also shall list your device. It should contain the name of the platform, then composite PLATFORM flag and whether
the device is publicly available or not.
Update .shippable.yml
Since we are using Shippable to test pull requests etc, we would like that you also add your device to the .shippable.yml
file, so that it will at least be built when someone is doing a pull request. Add a line at the end of file:
- _make PLATFORM=<platform-name>_
Maintainer
If you are submitting the board support upstream and cannot give Linaro maintainers a device, then we are going
to ask you to become the maintainer for the device you have added. This means that you should also update the
MAINTAINERS.md file accordingly. By being a maintainer for a device you are responsible to keep it up to date and
you will be asked every quarter as part of the OP-TEE release schedule to test your device running the latest OP-TEE
software.
Update build.git
This isn’t strictly necessary, but we are trying to create repo setup(s) for the device(s) that we are in charge of. That
makes it very easy for newcomers to get started with a certain platform. So please consider creating a new manifest:
for the device you have added to OP-TEE.
Most devices have some kind of Hardware Unique Key (HUK) that is mainly used to derive other keys. The HUK
could for example be used when deriving keys used in secure storage etc. The important thing with the HUK is that
it needs to be well protected and in the best case the HUK should never ever be readable directly from software, not
even from the secure side. There are different solutions to this, crypto accelerator might have support for it or, it could
involve another secure co-processor.
In OP-TEE the HUK is just stubbed and you will see that in the function called
tee_otp_get_hw_unique_key() in core/include/kernel/tee_common_otp.h. In a real se-
cure product you must replace this with something else. If your device lacks the hardware support for a HUK, then
you must at least change this to something else than just zeroes. But, remember it is not good secure practice to store
a key in software, especially not the key that is the root for everything else, so this is not something we recommend
that you should do.
The Time API in GlobalPlatform Internal Core API specification defines three sources of time; system time, TA
persistent time and REE time. The REE time is by nature considered as an unsecure source of time, but the other
two should in a fully trustable hardware make use of trustable source of time, i.e., a secure clock. Note that from
GlobalPlatform point of view it is not required to make use of a secure clock, i.e., it is OK to use time from
REE, but the level of trust should be reflected by the gpd.tee.systemTime.protectionLevel property
and the gpd.tee.TAPersistentTime.protectionLevel property (100=REE controlled clock, 1000=TEE
controlled clock). So the functions that one needs to pay attention to are tee_time_get_sys_time() and
tee_time_get_ta_time(). If your hardware has a secure clock, then you probably want to change the imple-
mentation there to instead use the secure clock (and then you would also need to update the property accordingly, i.e.,
tee_time_get_sys_time_protection_level() and the variable ta_time_prot_lvl in tee_svc.
c).
To be able to assure that your devices are running the (untampered) binaries you intended to run you will need to
establish some kind of trust anchor on the devices.
The most common way of doing that is to put the root public key in some read only memory on the device. Quite often
SoC’s/OEM’s stores public key(s) directly or the hash(es) of the public key(s) in OTP. When the boot ROM (which
indeed needs to be ROM) is about to load the first stage bootloader it typically reads the public key from the software
binary itself, hash the key and compare it to the key in OTP. If they are matching, then the boot ROM can be sure that
the first stage bootloader was indeed signed with the corresponding private key.
In OP-TEE you will not find any code at all related to this and this is a good example when it is hard for us to do this
in a generic way since device manufacturers all tend to do this in their own unique way and they are not very keen on
sharing their low level boot details and security implementation with the rest of the world. This is especially true on
ARMv7-A. For ARMv8-A it looks bit better, since Arm in Trusted Firmware A have implemented and defined how a
abstract the chain of trust (see auth-framework.rst). We have successfully verified OP-TEE by using the authentication
framework from Trusted Firmware A (see optee_with_auth_framework.md for the details).
By default OP-TEE uses a software crypto library (currently LibTomCrypt) and you have the ability to enable Crypto
Extensions that were introduced with ARMv8-A (if the device is capable of that). Some of the devices we have in
our hands do have hardware crypto IP’s, but due to NDA’s etc it has not been possible to enable it. If you have a
device capable of doing crypto operations on a dedicated crypto block and you prefer to use that in favor for the
software implementation, then you will need to implement relevant functions defined in core/include/crypto/
crypto.h, the Crypto API, and write the low level driver that communicates with the device. Our FIXME: Sphinx
[crypto.md] file describes how the Crypto API is integrated. Since the communication with crypto blocks tends to be
quite different depending on what kind of crypto block you have, we have not written how that should be done. It
might be that we do that in the future when get hold of a device where we can use the crypto block.
By default OP-TEE is configured with a software PRNG. The entropy is added to software PRNG at various places,
but unfortunately it is still quite easy to predict the data added as entropy. As a consequence, unless the RNG is based
on hardware the generated random will be quite weak.
In section 2 when we talked about the file main.c, we added a couple of handlers related to power management, we
are talking about the following lines:
.cpu_on = cpu_on_handler,
.cpu_off = pm_do_nothing,
.cpu_suspend = pm_do_nothing,
.cpu_resume = pm_do_nothing,
.system_off = pm_do_nothing,
.system_reset = pm_do_nothing,
The only function that actually does something there is the cpu_on function, the rest of them are stubbed. The main
reason for that is because we think that how to suspend and resume is a device dependent thing. The code in OP-TEE
is prepared so that callbacks etc from Trusted Firmware A will be routed to OP-TEE, but since the function(s) are just
stubbed we will not do anything and just return. In a real production device, you would probably want to save and
restore CPU states, secure hardware IPs’ registers and TZASC and other memory firewall related setting when these
callbacks are being called.
Arm have defined a system IP / SoC peripheral called TrustZone Address Space Controller (TZASC, see TZC-380 and
TZC-400). TZASC can be used to configure DDR memory into separate regions in the physcial address space, where
each region can have an individual security level setting. After enabling TZASC, it will perform security checks on
transactions to memory or peripherals. It is not always the case that TZASC is on a device, in some cases the SoC has
developed something equivalent. In OP-TEE this is very well reflected, i.e., different platforms have different ways of
protecting their memory. On ARMv8-A platforms we are in most of the cases using Trusted Firmware A as the boot
firmware and there the secure bootloader is the one that configures secure vs non-secure memory using TZASC (see
plat_arm_security_setup in TF-A). The takeaway here is that you must make sure that you have configured whatever
memory firewall your device has such that it has a secure and a non-secure memory area.
By default all Trusted Applications (TA’s) are signed with the pre-generated 2048-bit RSA development key (private
key). This key is located in the keys folder (in the root of optee_os.git) and is named default_ta.pem. This key
must be replaced with your own key and you should never ever check-in this private key in the source code tree when
in use in a real product. The recommended way to store private keys is to use some kind of HSM (Hardware Security
Module), but an alternative would be temporary put the private key on a computer considered as secure when you are
about to sign TA’s intended to be used in real products. Typically it is only a few number of people having access to
this type of key in company. The key handling in OP-TEE is currently a bit limited since we only support a single
key which is used for all TA’s. We have plans on extending this to make it a bit more flexible. Exactly when that will
happen has not been decided yet.
This section gives a brief description on how to enable the verification of OP-TEE using the authentication framework
in Trusted Firmware A (TF-A), i.e., something that could be used in an Armv8-A environment.
According to user-guide.rst, there is no additional specific build options for the verification of OP-TEE. If we have
enabled the authentication framework and specified the BL32 build option when building TF-A, the BL32 related
certificates will be created automatically by the cert_create tool, and then these certificates will be verified during
booting up.
To enable the authentication framework, the following steps should be followed according to user-guide.rst. For more
details about the authentication framework, please see auth-framework.rst and trusted-board-boot.rst.
• Check out a recent version of the mbed TLS repository and then switch to tag mbedtls-2.2.0
• Besides the normal build options, add the following build options for TF-A
Above steps have been tested on FVP platform, all verification steps are OK and xtest runs successfully without
regression.
Unlike for Armv8-A systems where one can use a more standardized way of doing secure boot by leverage the authen-
tication framework as described above, most device manufacturers have their own way of doing secure boot. Please
reach out directly to the manufacturer for the device you are working with to be able to understand how to do secure
boot on their devices.
2.8.1 Background
Secure Storage in OP-TEE is implemented according to what has been defined in GloblaPlatform’s TEE Internal Core
API (here called Trusted Storage). This specification mandates that it should be possible to store general-purpose data
and key material that guarantees confidentiality and integrity of the data stored and the atomicity of the operations that
modifies the storage (atomicity here means that either the entire operation completes successfully or no write is done).
There are currently two secure storage implementations in OP-TEE:
• The first one relies on the normal world (REE) file system. It is described in this document and is the default
implementation. It is enabled at compile time by CFG_REE_FS=y.
• The second one makes use of the Replay Protected Memory Block (RPMB) partition of an eMMC device, and is
enabled by setting CFG_RPMB_FS=y. It is described in [secure_storage_rpmb.md](secure_storage_rpmb.md).
It is possible to use the normal world file systems and the RPMB implementations simultaneously. For
this, two OP-TEE specific storage identifiers have been defined: TEE_STORAGE_PRIVATE_REE and
TEE_STORAGE_PRIVATE_RPMB. Depending on the compile-time configuration, one or several values may be used.
The value TEE_STORAGE_PRIVATE selects the REE FS when available, otherwise the RPMB FS (in this order).
When a TA is calling the write function provided by GP Trusted Storage API to write data to a persistent object, a
corresponding syscall implemented in TEE Trusted Storage Service will be called, which in turn will invoke a series of
TEE file operations to store the data. TEE file system will then encrypt the data and send REE file operation commands
and the encrypted data to TEE supplicant by a series of RPC messages. TEE supplicant will receive the messages and
store the encrypted data accordingly to the Linux file system. Reading files are handled in a similar manner.
Below is an excerpt from the specification, listing the most vital requirements:
4. Each TA has access to its own storage space that is shared among all the
instances of that TA but separated from the other TAs.
If configured with CFG_RPMB_FS=y the protection against rollback is controlled by the TEE and is set to 1000. If
CFG_RPMB_FS=n, there’s no protection against rollback, and the protection level is set to 0.
OP-TEE by default uses /data/tee/ as the secure storage space in the Linux file system. Each persistent object
is assigned an internal identifier. It is an integer which is visible in the Linux file system as /data/tee/<file
number>.
A directory file, /data/tee/dirf.db, lists all the objects that are in the secure storage. All normal world files are
integrity protected and encrypted, as described below.
Key manager is an component in TEE file system, and is responsible for handling data encryption and decryption and
also management of the sensitive key materials. There are three types of keys used by the key manager: the Secure
Storage Key (SSK), the TA Storage Key (TSK) and the File Encryption Key (FEK).
SSK is a per-device key and is generated and stored in secure memory when OP-TEE is booting. SSK is used to derive
the TA Storage Key (TSK).
SSK is derived by
SSK = HMACSHA256 (HUK, Chip ID || “static string”)
The functions to get Hardware Unique Key (HUK) and chip ID depend on platform implementation. Currently, in
OP-TEE OS we only have a per-device key, SSK, which is used for secure storage subsystem, but, for the future we
might need to create different per-device keys for different subsystems using the same algorithm as we generate the
SSK; An easy way to generate different per-device keys for different subsystems is using different static strings to
generate the keys.
The TSK is a per-Trusted Application key, which is generated from the SSK and the TA’s identifier (UUID). It is used
to protect the FEK, in other words, to encrypt/decrypt the FEK.
TSK is derived by:
TSK = HMACSHA256 (SSK, TA_UUID)
When a new TEE file is created, key manager will generate a new FEK by PRNG (pesudo random number genera-
tor) for the TEE file and store the encrypted FEK in meta file. FEK is used for encrypting/decrypting the TEE file
information stored in meta file or the data stored in block file.
The hash tree is responsible for handling data encryption and decryption of a secure storage file. The hash tree
is implemented as a binary tree where each node (struct tee_fs_htree_node_image below) in the tree
protects its two child nodes and a data block. The meta data is stored in a header (struct tee_fs_htree_image
below) which also protects the top node.
All fields (header, nodes, and blocks) are duplicated with two versions, 0 and 1, to ensure atomic updates. See
core/tee/fs_htree.c for details.
A new meta IV will be generated by PRNG when a meta data needs to be updated. The size of meta IV is defined
in core/include/tee/fs_htree.h, likewise are the data structures of meta data and node data are defined in fs_htree.h as
follows:
struct tee_fs_htree_node_image {
uint8_t hash[TEE_FS_HTREE_HASH_SIZE];
uint8_t iv[TEE_FS_HTREE_IV_SIZE];
uint8_t tag[TEE_FS_HTREE_TAG_SIZE];
uint16_t flags;
};
struct tee_fs_htree_meta {
uint64_t length;
};
struct tee_fs_htree_imeta {
struct tee_fs_htree_meta meta;
uint32_t max_node_id;
};
struct tee_fs_htree_image {
uint8_t iv[TEE_FS_HTREE_IV_SIZE];
uint8_t tag[TEE_FS_HTREE_TAG_SIZE];
uint8_t enc_fek[TEE_FS_HTREE_FEK_SIZE];
uint8_t imeta[sizeof(struct tee_fs_htree_imeta)];
uint32_t counter;
};
A new block IV will be generated by PRNG when a block data needs to be updated. The size of block IV is defined in
core/include/tee/fs_htree.h.
According to GlobalPlatform Trusted Storage requirement of the atomicity, the following operations should support
atomic update:
The strategy used in OP-TEE secure storage to guarantee the atomicity is out-of-place update.
This document describes the RPMB secure storage implementation in OP-TEE, which is enabled by setting
CFG_RPMB_FS=y. Trusted Applications may use this implementation by passing a storage ID equal to
TEE_STORAGE_PRIVATE_RPMB, or TEE_STORAGE_PRIVATE if CFG_REE_FS is disabled. For details about
RPMB, please refer to the JEDEC eMMC specification (JESD84-B51).
The architecture is depicted below.
For information about the ioctl() interface to the MMC/SD subsystem in the Linux kernel, see the Linux core
MMC header file linux/mmc/core.h and the mmc-utils repository.
This part is common with the REE-based filesystem. The interface between the system calls in
core/tee/tee_svc_storage.c and the RPMB filesystem is the tee_file_operations, namely struct tee_file_ops.
The FS implementation is entirely in core/tee/tee_rpmb_fs.c and the RPMB partition is divided in three parts:
• The first 128 bytes are reserved for partition data (struct rpmb_fs_partition).
• At offset 512 is the File Allocation Table (FAT). It is an array of struct rpmb_fat_entry elements, one
per file. The FAT grows dynamically as files are added to the filesystem. Among other things, each entry has
the start address for the file data, its size, and the filename.
• Starting from the end of the RPMB partition and extending downwards is the file data area.
Space in the partition is allocated by the general-purpose allocator functions, tee_mm_alloc() and
tee_mm_alloc2().
All file operations are atomic. This is achieved thanks to the following properties:
• Writing one single block of data to the RPMB partition is guaranteed to be atomic by the eMMC specification.
• The FAT block for the modified file is always updated last, after data have been written successfully.
• Updates to file content is done in-place only if the data do not span more than the “reliable write block count”
blocks. Otherwise, or if the file needs to be extended, a new file is created.
Device access
There is no eMMC controller driver in OP-TEE. The device operations all have to go through the normal world. They
are handled by the tee-supplicant process which further relies on the kernel’s ioctl() interface to access the
device. tee-supplicant also has an emulation mode which implements a virtual RPMB device for test purposes.
RPMB operations are the following:
Encryption
The FS encryption routines are in core/tee/tee_fs_key_manager.c. Block encryption protects file data. The algorithm
is 128-bit AES in Cipher Block Chaining (CBC) mode with Encrypted Salt-Sector Initialization Vector (ESSIV), see
CBC-ESSIV for details.
• During OP-TEE initialization, a 128-bit AES Secure Storage Key (SSK) is derived from a Hardware
Unique Key (HUK). It is kept in secure memory and never written to disk. A Trusted Application
Storage Key is derived from the SSK and the TA UUID.
• For each file, a 128-bit encrypted File Encryption Key (FEK) is randomly generated when the file is
created, encrypted with the TSK and stored in the FAT entry for the file.
• Each 256-byte block of data is then encrypted in CBC mode. The initialization vector is obtained by
the ESSIV algorithm, that is, by encrypting the block number with a hash of the FEK. This allows
direct access to any block in the file, as follows:
SSK, TSK and FEK handling is common with the REE-based secure storage, while the AES CBC block encryption is
used only for RPMB (the REE implementation uses GCM). The FAT is not encrypted.
If configured with both CFG_REE_FS=y and CFG_RPMB_FS=y the REE FS will create a special file, dirfile.
db.hash in RPMB which hold a hash representing the state of REE FS.
Warning: Currently no OP-TEE platform is able to support retrieval of the Hardware Unique Key or Chip
ID required for secure operation. For all platforms, a constant key is used, resulting in no protection against
decryption, or Secure Storage duplication to other devices. This is because information about how to retrieve key
data from the SoC is considered sensitive by the vendors and it is not freely available.
In OP-TEE, there are APIs for reading keys generically from One-Time-Programmable (OTP) memory. But there
are no existing platform implementations.
To allow Secure Storage to operate securely on your platform, you must define implementations in your platform code
for:
These implementations should fetch the key data from your SoC-specific e-fuses, or crypto unit according to the
method defined by your SoC vendor.
2.8.8 References
For more information about secure storage, please see SFO15-503, LAS16-504, SFO17-309 at Presentations and the
TEE Internal Core API specification.
There are two ways to implement Trusted Applications (TAs), Pseudo TAs and user mode TAs. User mode TAs are
full featured Trusted Applications as specified by the GlobalPlatform API TEE specifications, these are simply the
ones people are referring to when they are saying “Trusted Applications” and in most cases this is the preferred type
of TA to write and use.
These are implemented directly to the OP-TEE core tree in, e.g., core/arch/arm/pta and are built along with
and statically built into the OP-TEE core blob.
The Pseudo Trusted Applications included in OP-TEE already are OP-TEE secure privileged level services hidden
behind a “GlobalPlatform TA Client” API. These Pseudo TAs are used for various purposes such as specific secure
services or embedded tests services.
Pseudo TAs do not benefit from the GlobalPlatform Core Internal API support specified by the GlobalPlatform TEE
specs. These APIs are provided to TAs as a static library each TA shall link against (the “libutee”) and that calls
OP-TEE core service through system calls. As OP-TEE core does not link with libutee, Pseudo TAs can only use the
OP-TEE core internal APIs and routines.
As Pseudo TAs runs at the same privileged execution level as the OP-TEE core code itself and that might or might not
be desirable depending on the use case.
In most cases an unprivileged (user mode) TA is the best choice instead of adding your code directly to the OP-TEE
core. However if you decide your application is best handled directly in OP-TEE core like this, you can look at core/
arch/arm/pta/stats.c as a template and just add your Pseudo TA based on that to the sub.mk in the same
directory.
User Mode Trusted Applications are loaded (mapped into memory) by OP-TEE core in the Secure World when some-
thing in Rich Execution Environment (REE) wants to talk to that particular application UUID. They run at a lower
CPU privilege level than OP-TEE core code. In that respect, they are quite similar to regular applications running in
the REE, except that they execute in Secure World.
Trusted Application benefit from the GlobalPlatform TEE Internal Core API as specified by the GlobalPlatform TEE
specifications. There are several types of user mode TAs, which differ by the way they are stored.
2.9.3 TA locations
Plain TAs (user mode) can reside and be loaded from various places. There are three ways currently supported in
OP-TEE.
Early TA
The so-called early TAs are virtually identical to the REE FS TAs, but instead of being loaded from the Normal World
file system, they are linked into a special data section in the TEE core blob. Therefore, they are available even before
tee-supplicant and the REE’s filesystems have come up. Please find more details in the early TA commit.
REE filesystem TA
They consist of a cleartext signed ELF file, named from the UUID of the TA and the suffix .ta. They are built
separately from the OP-TEE core boot-time blob, although when they are built they use the same build system, and
are signed with the key from the build of the original OP-TEE core blob.
Because the TAs are signed, they are able to be stored in the untrusted REE filesystem, and tee-supplicant will
take care of passing them to be checked and loaded by the Secure World OP-TEE core. Note that this type of TA isn’t
encrypted.
Secure Storage TA
These are stored in secure storage. The meta data is stored in a database of all installed TAs and the actual binary is
stored encrypted and integrity protected as a separate file in the untrusted REE filesystem (flash). Before these TAs
can be loaded they have to be installed first, this is something that can be done during initial deployment or at a later
stage.
For test purposes the test program xtest can install a TA into secure storage with the command:
$ xtest --install-ta
Yada yada
The OP-TEE build system is based on GNU make and CMake. It consists of a main Makefile in the root of the
project together with sub.mk files in all source directories. In addition, some supporting files are used to recursively
process all sub.mk files and generate the build rules.
Name Description
core/core.mk Included from Makefile to build the TEE Core
ta/ta.mk Included from Makefile to create the TA devkit
mk/compile.mk Create rules to make objects from source files
mk/lib.mk Create rules to make a libraries (.a)
mk/subdir.mk Process sub.mk files recursively
mk/config.mk Global configuration variable
core/arch/$(ARCH)/$(ARCH).mk Arch-specific compiler flags
core/arch/$(ARCH)/ Platform-specific compiler flags and configuration variables
plat-$(PLATFORM)/conf.mk
core/arch/$(ARCH)/ Make recipes to link the TEE Core
plat-$(PLATFORM)/link.mk
ta/arch/arm/link.mk Make recipes to link Trusted Applications
ta/mk/ta_dev_kit.mk Main Makefile to be included when building Trusted Applica-
tions
mk/checkconf.mk Utility functions to manipulate configuration variables and gen-
erate a C header file
sub.mk List source files and define compiler flags
make is always invoked from the top-level directory; there is no recursive invocation of make itself.
67
OP-TEE Documentation
The target architecture, platform and build directory may be selected by setting environment or make variables
(VAR=value make or make VAR=value).
ARCH - CPU architecture
$(ARCH) is the CPU architecture to be built. Currently, the only supported value is arm for 32-bit or 64-bit
Armv7-A or Armv8-A. Please note that contrary to the Linux kernel, $(ARCH) should not be set to arm64 for
64-bit builds. The ARCH variable does not need to be set explicitly before building either, because the proper in-
struction set is selected from the $(PLATFORM) value. For platforms that support both 32-bit and 64-bit builds,
CFG_ARM64_core=y should be set to select 64-bit and not set (or set to n) to select 32-bit.
Architecture-specific source code belongs to sub-directories that follow the arch/$(ARCH) pattern, such as: core/
arch/arm, lib/libmpa/arch/arm, lib/libutee/arch/arm and so on.
CROSS_COMPILE
$(CROSS_COMPILE) is the prefix used to invoke the (32-bit) cross-compiler toolchain. The default value is
arm-linux-gnueabihf-. This is the variable you want to change in case you want to use ccache to speed
you recompilations:
If the build includes a mix of 32-bit and 64-bit code, for instance if you set CFG_ARM64_core=y to build a
64-bit secure kernel, then two different toolchains are used, that are controlled by $(CROSS_COMPILE32) and
$(CROSS_COMPILE64). The default value of $(CROSS_COMPILE32) is the value of CROSS_COMPILE, which
defaults to arm-linux-gnueabihf- as mentioned above. The default value of $(CROSS_COMPILE64) is
aarch64-linux-gnu-. Examples:
# For this example, select HiKey which supports both 32- and 64-bit builds
$ export PLATFORM=hikey
# 3. Same as (2.)
$ make CROSS_COMPILE32="ccache arm-linux-gnueabihf-"
# 4. Select 64-bit secure 'core' (and therefore both 32- and 64-bit
# Trusted Application libraries)
$ make CFG_ARM64_core=y
PLATFORM / PLATFORM_FLAVOR
A platform is a family of closely related hardware configurations. A platform flavor is a variant of such configurations.
When used together they define the target hardware on which OP-TEE will be run.
For instance PLATFORM=stm PLATFORM_FLAVOR=b2260 will build for the ST Microelectronics
96boards/cannes2 board, while PLATFORM=vexpress PLATFORM_FLAVOR=qemu_virt will generate
code for a para-virtualized Arm Versatile Express board running on QEMU.
For convenience, the flavor may be appended to the platform name with a dash, so make PLATFORM=stm-b2260
is a shortcut for make PLATFORM=stm PLATFORM_FLAVOR=b2260. Note that in both cases the value of
$(PLATFORM) is stm in the makefiles.
Platform-specific source code belongs to core/arch/$(ARCH)/plat-$(PLATFORM), for instance: core/
arch/arm/plat-vexpress or core/arch/arm/plat-stm.
O - output directory
All output files go into a platform-specific build directory, which is by default out/
$(ARCH)-plat-$(PLATFORM).
The output directory has basically the same structure as the source tree. For instance, assuming
ARCH=arm PLATFORM=stm, core/kernel/panic.c will compile into out/arm-plat-stm/core/
kernel/panic.o.
However, some libraries are compiled several times: once or twice for user mode, and once for kernel mode. This
is because they may be used by the TEE Core as well as by the Trusted Applications. As a result, the lib source
directory gives two or three build directories: ta_arm{32,64}-lib and core-lib.
The output directory also has an export-ta_arm{32,64} directory, which contains:
• All the files needed to build Trusted Applications.
– In lib/: libutee.a (the GlobalPlatform Internal API), libutils.a (which implements a part of
the standard C library), and libmpa.a (which implements multiple precision arithmetic and is required
by libutee.a).
– In include/: header files for the above libraries
– In mk/: ta_dev_kit.mk, which is a Make include file with suitable rules to build a TA, and its depen-
dencies
– scripts/sign.py: a Python script used by ta_dev_kit.mk to sign TAs.
– In src: user_ta_header.c: source file to add a suitable header to the Trusted Application (as ex-
pected by the loader code in the TEE Core).
• Some files needed to build host applications (using the Client API), under export-ta_arm{32,64}/
host_include.
Finally, the build directory contains the auto-generated configuration file for the TEE Core: $(O)/include/
generated/conf.h (see below).
• $(core-platform-subdirs) is the list of the subdirectories that are added to the TEE Core.
Linker scripts
The file core/arch/$(ARCH)/plat-$(PLATFORM)/link.mk contains the rules to link the TEE Core and
perform any related tasks, such as running objdump to produce a dump file. link.mk adds files to the all: target.
Source files
Each directory that contains source files has a file called sub.mk. This makefile defines the source files that should
be included in the build, as well as any subdirectories that should be processed, too. For example:
# core/arch/arm/sm/sub.mk
srcs-y += sm_asm.S
srcs-y += sm.c
# core/sub.mk
subdirs-y += kernel
subdirs-y += mm
subdirs-y += tee
subdirs-y += drivers
Compiler flags
Default compiler flags are defined in mk/compile.mk. Note that platform-specific flags must not appear in this file
which is common to all platforms.
To add flags for a given source file, you may use the following variables in sub.mk:
• cflags-<filename>-y for C files (*.c)
• aflags-<filename>-y for assembler files (*.S)
• cppflags-<filename>-y for both C and assembler
For instance:
# core/lib/libtomcrypt/src/pk/dh/sub.mk
srcs-y += dh.c
cflags-dh.c-y := -Wno-unused-variable
# lib/libutils/isoc/newlib/sub.mk
srcs-y += memmove.c
cflags-remove-memmove.c-y += -Wcast-align
Some variables apply to libraries only (that is, when using mk/lib.mk) and affect all the source files that belong to
the library: cppflags-lib-y and cflags-lib-y.
Include directories
Include directories may be added to global-incdirs-y, in which case they will be accessible from all the
source files and will be copied to export-ta_arm{32,64}/include and export-ta_arm{32,64}/
host_include.
When sub.mk is used to build a library, incdirs-lib-y may receive additional directories that will be used for
that library only.
Configuration variables
Some features may be enabled, disabled or otherwise controlled at compile time through makefile variables. De-
fault values are normally provided in makefiles with the ?= operator so that their value may be easily overridden by
environment variables. For instance:
PLATFORM ?= stm
PLATFORM_FLAVOR ?= default
Some global configuration variables are defined in mk/config.mk, but others may be defined in sub.mk when
then pertain to a specific library for instance.
Variables with the CFG_ prefix are treated in a special way: their value is automatically reflected in the generated
header file $(out-dir)/include/generated/conf.h, after all the included makefiles have been processed.
conf.h is automatically included by the preprocessor when a source file is built.
Depending on their value, variables may be considered either boolean or non-boolean, which affects how they are
translated into conf.h.
Boolean configuration variables
When a configuration variable controls the presence or absence of a feature, y means enabled, while n, empty value or
an undefined variable means disabled. For instance, the following commands are equivalent and would disable feature
CFG_CRYPTO_GCM:
$ make CFG_CRYPTO_GCM=n
$ make CFG_CRYPTO_GCM=
$ CFG_CRYPTO_GCM=n make
$ export CFG_CRYPTO_GCM=n
$ make
Configuration variables may then be used directly in sub.mk to trigger conditional compilation:
# core/lib/libtomcrypt/src/encauth/sub.mk
subdirs-$(CFG_CRYPTO_CCM) += ccm
subdirs-$(CFG_CRYPTO_GCM) += gcm
When a configuration variable is enabled (y), <generated/conf.h> contains a macro with the same name as
the variable and the value 1. If it is disabled, however, no macro definition is output. This allows the C code to use
constructs like:
/* core/lib/libtomcrypt/src/tee_ltc_provider.c */
/* ... */
(continues on next page)
#if defined(CFG_CRYPTO_GCM)
struct tee_gcm_state {
gcm_state ctx; /* the gcm state as defined by LTC */
size_t tag_len; /* tag length */
};
#endif
$ make CFG_TEE_CORE_LOG_LEVEL=4
/* out/arm-plat-vexpress/include/generated/conf.h */
Configuration dependencies
Some combinations of configuration variables may not be valid. This should be dealt with by custom checks in
makefiles. mk/checkconf.h provides functions to help detect and deal with such situations.
3.1.2 Buildroot
3.1.3 CMake
3.1.5 OpenEmbedded
3.2 Prerequisites
We believe that you can use any Linux distribution to build OP-TEE, but as maintainers of OP-TEE we are mainly
using Ubuntu-based distributions and to be able to build and run OP-TEE there are a few packages that needs to be
installed to start with. Therefore install the following packages regardless of what target you will use in the end.
These are the gits considered as the main OP-TEE gits which together makes up the entire TEE solution.
3.3.1 build
Why this particular git? As it turns out it’s totally possible to put together everything on your own. You can build
all the individual components, os, client, xtest, Linux kernel, TF-A, TianoCore, QEMU, Buildroot etc and put all the
binaries at correct locations and write your own command lines, Makefiles, shell-scripts etc that will work nicely on
the devices you are interested in. If you know how to do that, fine, please go a head. But for newcomers it’s way to
much behind the scenes to be able to setup a working environment. Also, if you for some reason want to run something
in an automated way, then you need something else wrapping it up for you.
With this particular git built.git our goal is to simply to make it easy for newcomers to get started with OP-TEE using
the devices we’ve listed in this document.
git location
https://github.com/OP-TEE/build
Why repo?
We discussed alternatives, initially we started out with having a simple shell-script, that worked to start with, but after
getting more gits in use and support for more devices it started to be difficult to maintain. In the end we ended up
choosing between repo from the Google AOSP project and git submodules. No matter which you choose, there will
always be some person arguing that one is better than the other. For us we decided to use repo. Not directly for
the features itself from repo, but for the ability to simply work with different manifests containing both stable and
non-stable release. Using some tips and tricks you can also speed up setup time significantly. For day to day work
with commits, branches etc we tend to use git commands directly.
Root FS
The root fs in the builds that we cover here are as small as possible and is based on a stripped down Buildroot
configuration adding just enough in the root fs such that one can:
• Boot OP-TEE.
• Run xtest with no regressions.
• Easily add additional developer tools like, strace, valgrind etc.
Note: As a consequence of enabling “just enough”, it is likely that non-UART based enviroments won’t work out of
the box. I.e., if you try to boot up an enviroment using HDMI and connect keyboards and other devices it is likely that
things will not work. To make them work, you probably need to rebuild Linux kernel with correct drivers/frameworks
enabled and in addition to that enable binaries/daemons in Buildroot that might be necessary (user space tools and
drivers).
For guides how to build AOSP, please refer to our AOSP page. For OpenEmbedded we have no guide ready, however
there are teams in Linaro who are building OP-TEE using OpenEmbedded. If you want to get in contact with them,
please reach out to us (see Contact).
Below is a table showing the platforms supported by build.git. OP-TEE as such supports many more platforms, but
since quite a few of the other platforms are maintained by people outside Linaro or are using a special setup, we
encourage you to talk to the maintainer of that platform directly if you have build related questions etc. Please see the
MAINTAINERS file for contact information.
Manifests
Current version
Here is a list of manifests for the devices currently supported in build.git. With these you will get a setup
containing the all necessary software components to run OP-TEE on the chosen device. Beware that this will run
latest available on OP-TEE gits meaning that if you re-sync then you will most likely get new commits. If you need a
stable/tagged version with non-moving gits, then please refer to the next section instead.
Stable releases
Starting from OP-TEE v3.1 you can check out stable releases by using the same manifests as for current version
above, but with the difference that you also need to specify a branch where the name corresponds to the release
version. I.e., when we are doing releases we are creating a branch with a name corresponding to the release version.
So, let’s for example say that you want to checkout a stable OP-TEE v3.2 for Raspberry Pi 3, then you do like this
instead of what is mentioned further down in section 7.3 (note the -b 3.2.0):
TODO
...
$ repo init -u https://github.com/OP-TEE/manifest.git -m rpi3.xml -b 3.2.0
...
Before OP-TEE v3.1 we used to have separate xml-manifest files for the stable builds. If you for some reason need
an older stable release, then you can use the xyz_stable.xml file corresponding to your device. The way to
init repo is almost the same as described above, the major difference is the name of manifest being referenced (-m
xyz_stable.xml) and that we are referring to a tag instead of a branch (-b refs/tags/MAJOR.MINOR.
PATCH). So as an example, if you need to setup the 2.1.0 stable release for HiKey, then you would do like this
instead of what is mentioned further down in section 7.3
TODO
...
repo init -u https://github.com/OP-TEE/manifest.git -m hikey_stable.xml -b refs/tags/
˓→2.1.0
...
Here is a list of targets and the names of the stable manifests files which were supported by older releases:
Below we will describe the general way of how to get the source, build the solution and how to run xtest on the device.
For device specific instructions, please see the links in the table in the Current version section.
Step 1 - Prerequisites
Note that here you don’t install a huge SDK, it’s simply a Python script that you download and put in your $PATH,
that’s it. Exactly how to “install” repo, could be found in the Google repo pages, so follow those instructions before
continuing.
Choose the manifest corresponding to the platform you intend to use (see the table in Current version. For example, if
you intend to use Raspberry Pi3, then at line 3 below, ${TARGET}.xml shall be rpi3.xml.
1 $ mkdir -p $HOME/devel/optee
2 $ cd $HOME/devel/optee
3 $ repo init -u https://github.com/OP-TEE/manifest.git -m ${TARGET}.xml [-b ${BRANCH}]
4 $ repo sync
Note: The repo sync step will take some time if you aren’t referencing an existing tree (see the Tips and Tricks
section).
In OP-TEE we’re using different toolchains for different targets (depends on ARMv7-A ARMv8-A 64/32bit solutions).
In any case start by downloading the toolchains by:
$ cd build
$ make toolchains
We’ve configured our repo manifests, so that repo will always automatically symlink the Makefile to the correct
device specific makefile, that means that you simply start the build by running:
$ make
This step will also take some time, but you can speed up subsequent builds by enabling ccache (again see Tips and
Tricks).
On non-emulated solutions, you will need to flash the software in some way. We’ve tried to “hide” that under the
following make target:
$ make flash
But, since some devices are trickier to flash than others, please see the Device specific build information. See this just
as a general instruction.
On some solutions tee-supplicant is already running (check by running $ ps aux | grep tee-supplicant)
on others not. If it’s not running, then start it by running:
$ tee-supplicant &
Note: If you’ve built using our manifest you should not need to modprobe any OP-TEE/TEE kernel driver since it’s
built into the kernel in all our setups.
The entire xtest test suite has been deployed when you we’re running $ make run in previous step, i.e, in general
there is no need to copy any binaries manually. Everything has been put into the Root FS automatically. So, to run
xtest, you simply type:
$ xtest
If there are no regressions / issues found, xtest should end with something like this:
...
+-----------------------------------------------------
23476 subtests of which 0 failed
67 test cases of which 0 failed
0 test case was skipped
TEE test application done!
Doing a repo init, repo sync from scratch can take a fair amount of time. The main reason for that is simply
because of the size of some of the gits we are using, like for the Linux kernel and EDK2. With repo you can reference
an existing forest and by doing so you can speed up repo sync to taking 20 seconds instead of an hour. The way to do
this are as follows.
1. Start by setup a clean forest that you will not touch, in this example, let us call that optee-ref and put that
under for $HOME/devel/optee-ref. This step will take somewhere between 15- to 45 minutes, depending
on your connection speed to internet.
2. Then setup a cronjob (crontab -e) that does a repo sync in this folder particular folder once a night (that
is more than enough).
3. Now you should setup your actual tree which you are going to use as your working tree. The way to do this
is almost the same as stated in the instructions above, the only difference is that you reference the other local
forest when running repo init, like this
4. The rest is the same above, but now it will only take a couple of seconds to clone a forest.
Normally ‘1’ and ‘2’ above is something you will only do once. Also if you ignore step ‘2’, then you will still get the
latest from official git trees, since repo will also check for updates that aren’t at the local reference.
Use ccache
ccache is a tool that caches build object-files etc locally on the disc and can speed up build time significantly in
subsequent builds. On Debian-based systems (Ubuntu, Mint etc) you simply install it by running:
The makefiles in build.git are configured to automatically find and use ccache if ccache is installed on your system, so
other than having it installed you don’t have to think about anything.
3.3.2 manifest
This page contains a couple of guidelines and rules that we want to try to follow when it comes to managing the
manifests.
git location
https://github.com/OP-TEE/manifest
Remotes
Since most of our projects can be found on GitHub, we are using that as the main remote. If you need to include other
remotes for some reason, then that is OK, but please double check of there is any maintained (and preferably official)
mirror for the project at GitHub before adding a new remote.
Sections
To have some kind of structure of the files, we have split them up in three sections, one for pure OP-TEE gits, one for
OP-TEE supporting gits found at linaro-swg and then a third, misc section where everything else can be found. I.e.,
a template looks like this (this also includes the default remote for clarity):
All <projects ... > lines should be on the format as shown below with the attributes in this order. The reason
for this is to have it uniformly done across all manifests and that it will make it easier when comparing various versions
of manifests with diff tools. All three attributes are mandatory. The only exception is revision which does not
have to be stated if it is master that we are tracking.
Alphabetic order
Within each of the three sections, all <project ... > lines shall be sorted in alphabetic order (this is again for
making it easier to diff manifests). The only expection here is build.git which uses the linkfile element.
Having that at the end makes it look cleaner.
If you are using another remote than the default, then that should come after the revision attribute (this is true for
all attributes other than the path, name and revision).
The three mandatory XML attributes path, name and revision should be column aligned. Alignment of additional
XML attributes are optional.
With clone-depth="1" you are telling repo and git that you only want a certain commit and not the entire git
log history. You can only use this under two conditions and that is when revision is either a branch or a tag. Pure
SHA-1's does not work and will even raise repo and git sync errors in some cases. So, the rules are, if you use
either revision="refs/tags/my_tag" or revision="refs/heads/my_branch", then you shall add
clone-depth="1" right after the revision attribute.
Spaces or tabs?
Example
Here is an example showing the basis for an OP-TEE manifest. The names are fictive etc, but it describes everything
said above.
</manifest>
3.3.3 optee_client
3.3.4 optee_examples
3.3.5 optee_os
3.3.6 optee_test
3.4.1 FVP
Foo bar
Foo bar
3.4.4 Juno
3.4.5 QEMU
3.4.6 QEMUv8
3.4.7 Raspberry Pi 3
Foo bar
3.4.8 TI devices
DRA7xx
AM57xx
AM43xx
3.6 AOSP
Debugging techniques
When OP-TEE encounters a serious error condition, it prints diagnostic information to the secure console. The mes-
sage contains a call stack if CFG_UNWIND=y (enabled by default).
The following errors will trigger a dump:
• Data or prefetch abort exception in the TEE core (kernel mode) or in a TA (user mode),
• When a user-mode Trusted Application panics, either by calling TEE_Panic() directly or due to some error
detected by the TEE Core Internal API,
• When the TEE core detects a fatal error and decides to hang the system because there is no way to proceed
safely (core panic).
The messages look slightly different depending on:
• Whether the error is an exception or a panic,
• The exception/privilege level when the exception occurred (PL0/EL0 if a user mode Trusted Application was
running, PL1/EL1 if it was the TEE core),
• Whether the TEE and TA are 32 or 64 bits,
• The exact type of exception (data or prefetch abort, translation fault, read or write permission fault, alignment
errors etc).
Here is an example of a panic in a 32-bit Trusted Application, running on a 32-bit TEE core (QEMU):
83
OP-TEE Documentation
The above dump was triggered by the TA when entering an irrecoverable error ending up in a TEE_Panic(0) call.
OP-TEE provides a helper script called symbolize.py to facilitate the analysis of such issues. It is located in the
OP-TEE OS source tree in scripts/symbolize.py and is also copied to the TA development kit. Whenever you
are confronted with an error message reporting a serious error and containing a "Call stack:" line, you may use
the symbolize script.
symbolize.py reads its input from stdin and writes extended debug information to stdout. The -d (directo-
ries) option tells the script where to look for TA ELF file(s) (<uuid>.stripped.elf) or for tee.elf (the TEE
core). Please refer to symbolize.py --help for details.
Typical output:
E/TC:0 region 2: va 0x110000 pa 0xe30f000 size 0x3000 flags r-- .rodata .ARM.extab .
˓→ARM.extab.text.utee_panic .ARM.extab.text.__aeabi_ldivmod .ARM.extab.text.__aeabi_
E/TC:0 region 3: va 0x113000 pa 0xe312000 size 0xb000 flags rw- .data .bss
E/TC:0 region 4: va 0 pa 0 size 0 flags ---
E/TC:0 region 5: va 0 pa 0 size 0 flags ---
E/TC:0 region 6: va 0 pa 0 size 0 flags ---
E/TC:0 region 7: va 0 pa 0 size 0 flags ---
E/TC:0 Call stack:
E/TC:0 0x001044a8 utee_panic at optee_os/lib/libutee/arch/arm/utee_syscalls_a32.S:74
E/TC:0 0x0010ba59 TEE_Panic at optee_os/lib/libutee/tee_api_panic.c:35
E/TC:0 0x00101093 hmac_sha1 at optee_examples/hotp/ta/hotp_ta.c:63
E/TC:0 0x001013ed get_hotp at optee_examples/hotp/ta/hotp_ta.c:171
E/TC:0 0x00101545 TA_InvokeCommandEntryPoint at optee_examples/hotp/ta/hotp_ta.c:225
E/TC:0 0x0010441b entry_invoke_command at optee_os/lib/libutee/arch/arm/user_ta_
˓→entry.c:207
The Python script uses several tools from the GNU Binutils package to perform the following tasks:
1. Translate the call stack addresses into function names, file names and line numbers.
2. Convert the abort address to a symbol plus some offset and/or an ELF section name plus some offset.
3. Print the names of the ELF sections contained in each memory region of a TA.
Note that to successfully run symbolize.py you must also make your toolchain visible on the PATH (i.e., export
PATH=<my-toolchain-path>/bin:$PATH).
Due to its nature, OP-TEE is being a solution spanning over several architectural layers, where each layer includes its
own complex parts. For further optimizations of performance, there is a need of tool which will provide detailed and
precise profiling information for each layer.
It is necessary to receive latency values for:
• The roundtrip time for going from a client application in normal world, down to a Trusted Application and back
again.
• Detailed information for amount of time taken to go through each layer:
– libTEEC -> Linux OP-TEE kernel driver
– Linux OP-TEE kernel driver -> OP-TEE OS Core
– OP-TEE OS Core -> TA entry point (not supported yet)
– The same way back
Design overview
When the benchmark is enabled, all OP-TEE layers (libTEEC, Linux kernel OP-TEE driver, OP-TEE OS core) do fill
the registered timestamp circular buffer with timestamp data for all invocation requests on condition that the circular
buffer is allocated/registered.
Timestamp source
Arm Performance Monitor Units are used as the main source of timestamp values. The reason why this technology
was chosen is that it is supported on all Armv7-A/Armv8-A cores. Besides it can provide precise pre-cpu cycle counter
values, it is possible to enable EL0 access to all events, so usermode applications can directly read cpu counter values
from coprocessor registers, achieving minimal latency by avoiding additional syscalls to EL1 core.
Besides CPU cycle counter values, timestamp by itself contains also information about:
• Executing CPU core index
• OP-TEE layer id, where this timestamp was obtained from
• Program counter value when timestamp was logged, which can be used for getting a symbol name (a filename
and line number)
Before using Benchmark framework, OP-TEE should be rebuild with CFG_TEE_BENCHMARK flag enabled.
$ make CFG_TEE_BENCHMARK=y
When client_app finishes its execution, optee_benchmark will generate <client_app>.ts timestamp data file in
the same directory, where CA is stored.
Currently, timestamping is done only for InvokeCommand calls, but it’s also possible to choose custom places in
the supported OP-TEE layers. To add timestamp storing command to custom c source file:
1. Include appropriate header:
• OP-TEE OS Core: bench.h
• Linux kernel OP-TEE module: optee_bench.h
• libTEEC: teec_benchmark.h
2. Invoke bm_timestamp() (for linux kmod use optee_bm_timestamp()) in the function, where you want
to put timestamp from.
Analyzing results
• Implementation of application which will analyze timestamp data and provide statistics for different types of
calls providing avg/min/max values (both CPU cycles and time values).
• Add support for all platforms, where OP-TEE is supported.
• Adding support of S-EL0 timestamping.
• Attaching additional payload information to each timestamp, for example, session.
• Timestamping within interrupt context in the OP-TEE OS Core.
4.3 Gprof
4.3. Gprof 89
OP-TEE Documentation
4.3.1 Usage
• Build OP-TEE OS with CFG_TA_GPROF_SUPPORT=y. You may also set CFG_ULIBS_GPROF=y to instru-
ment the user TA libraries (libutee, libutils, libmpa).
• Build user TAs with -pg, for instance using: CFLAGS_ta_arm32=-pg or CFLAGS_ta_arm64=-pg. Note
that instrumented TAs have a larger .bss section. The memory overhead is 1.36 times the .text size for 32-
bit TAs, and 1.77 times for 64-bit ones (refer to the TA linker script for details: ta/arch/arm/ta.ld.S).
• Run the application normally. When the last session exits, tee-supplicant will write profiling
data to /tmp/gmon-<ta_uuid>.out. If the file already exists, a number is appended, such as:
gmon-<ta_uuid>.1.out.
• Run gprof on the TA ELF file and profiling output: gprof <ta_uuid>.elf gmon-<ta_uuid>.out
4.3.2 Implementation
Part of the profiling is implemented in libutee. Another part is done in the TEE core by a pseudo-TA (core/arch/
arm/sta/gprof.c). Two types of data are collected:
1. Call graph information
• When TA source files are compiled with the -pg switch, the compiler generates extra code into each
function prologue to call the instrumentation entry point (__gnu_mcount_nc or _mcount de-
pending on the architecture). Each time an instrumented function is called, libutee records a pair of
program counters (one is the caller and the other one is the callee) as well as the number of times this
specific arc of the call graph has been invoked.
2. PC distribution over time
• When an instrumented TA starts, libutee calls the pseudo-TA to start PC sampling for the current
session. Sampling data are written into the user-space buffer directly by the TEE core.
• Whenever the TA execution is interrupted, the TEE core records the current program counter value
and builds a histogram of program locations (i.e., relative amount of time spent for each value of the
PC). This is later used by the gprof tool to derive the time spent in each function. The sampling rate,
which is assumed to be roughly constant, is computed by keeping track of the time spent executing
user TA code and dividing the number of interrupts by the total time.
• The profiling buffer into which call graph and sampling data are recorded is allocated in the TA’s
.bss section. Some space is reserved by the linker script, only when the TA is instrumented.
4.4 GDB
4.5 JTAG
4.5.1 OpenOCD
5.1.2 Building
5.1.3 License
5.1.6 Interfaces
5.1.7 Architecture
5.1.9 Testing
91