diff --git a/Documentation/index.rst b/Documentation/index.rst
index c6a3c894..b1954dfd 100644
--- a/Documentation/index.rst
+++ b/Documentation/index.rst
@@ -52,6 +52,12 @@ Table of Contents
sgx-intro
glossary
+.. toctree::
+ :caption: Tutorials
+ :maxdepth: 2
+
+ tutorials/pytorch/index.rst
+
.. toctree::
:caption: Manual pages
:maxdepth: 1
diff --git a/Documentation/tutorials/pytorch/img/intro-01.svg b/Documentation/tutorials/pytorch/img/intro-01.svg
new file mode 100644
index 00000000..8a18b81f
--- /dev/null
+++ b/Documentation/tutorials/pytorch/img/intro-01.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/Documentation/tutorials/pytorch/img/workflow.svg b/Documentation/tutorials/pytorch/img/workflow.svg
new file mode 100644
index 00000000..af6c507e
--- /dev/null
+++ b/Documentation/tutorials/pytorch/img/workflow.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Documentation/tutorials/pytorch/index.rst b/Documentation/tutorials/pytorch/index.rst
new file mode 100644
index 00000000..78cb3eba
--- /dev/null
+++ b/Documentation/tutorials/pytorch/index.rst
@@ -0,0 +1,573 @@
+PyTorch PPML Framework Tutorial
+===============================
+
+.. highlight:: sh
+
+This tutorial presents a framework for developing PPML (Privacy-Preserving
+Machine Learning) applications with Intel SGX and Graphene. We use `PyTorch
+`__ as an example ML framework. However, this tutorial can
+be applied to other ML frameworks like OpenVINO, TensorFlow, etc.
+
+Introduction
+------------
+
+Machine Learning (ML) is increasingly utilized in many real-world applications.
+ML algorithms are first trained on massive amounts of known past data and then
+deployed to interpret unknown future data, which allows us to forecast weather,
+classify images, recommend content, and so on.
+
+As machine learning pervades our daily lives, privacy concerns emerge as one of
+the key issues about this technology. In this tutorial, we focus on protecting
+the confidentiality and integrity of the input data when the computation takes
+place on an untrusted platform such as a public cloud virtual machine. We also
+protect the model for cases where the model owner is concerned about protecting
+their IP. In particular, we highlight how to build the PPML framework based on
+PyTorch in an untrusted cloud using Intel SGX and Graphene.
+
+.. image:: ./img/intro-01.svg
+ :target: ./img/intro-01.svg
+ :alt: Figure: Training and inference in ML workloads
+
+In general, ML workloads have two phases: training and inference. Both can be
+viewed as an application that takes inputs and produces an output. Training
+applications take a training dataset as input and produce a trained model.
+Inference applications take new data and the trained model as inputs and produce
+the result (the prediction).
+
+The goal of this tutorial is to show how these applications -- PyTorch workloads
+in particular -- can run in an untrusted environment (like a public cloud),
+while still ensuring the confidentiality and integrity of sensitive input data
+and the model. To this end, we use Intel SGX enclaves to isolate PyTorch's
+execution to protect data confidentiality and integrity, and to provide a
+cryptographic proof that the program is correctly initialized and running on
+legitimate hardware with the latest patches. We also use Graphene to simplify
+the task of porting PyTorch to SGX, without any changes to the ML application
+and scripts.
+
+.. image:: ./img/workflow.svg
+ :target: ./img/workflow.svg
+ :alt: Figure: Complete workflow of PyTorch with Graphene
+
+In this tutorial, we will show the complete workflow for PyTorch running inside
+an SGX enclave using Graphene and its features of Secret Provisioning and
+Protected Files. We rely on the new ECDSA/DCAP remote attestation scheme
+developed by Intel for untrusted cloud environments.
+
+To run the PyTorch application on a particular SGX platform, the owner of the
+SGX platform must retrieve the corresponding SGX certificate from the Intel
+Provisioning Certification Service, along with Certificate Revocation Lists
+(CRLs) and other SGX-identifying information (1). Typically, this is a part of
+provisioning the SGX platform in a cloud or a data center environment, and the
+end user can access it as a service (in other words, the end user doesn't need
+to deal with the details of this SGX platform provisioning but instead uses a
+simpler interface provided by the cloud/data center vendor).
+
+As a second preliminary step, the user must encrypt the input and model files
+with her cryptographic (wrap) key and send these protected files to the remote
+storage accessible from the SGX platform (2).
+
+Next, the remote platform starts PyTorch inside of the SGX enclave. Meanwhile,
+the user starts the secret provisioning application on her own machine. The two
+machines establish a TLS connection using RA-TLS (3), the user verifies that the
+remote platform has a genuine up-to-date SGX processor and that the application
+runs in a genuine SGX enclave (4), and finally provisions the cryptographic wrap
+key to this remote platform (5). Note that during build time, Graphene informs
+the user of the expected measurements of the SGX application.
+
+After the cryptographic wrap key is provisioned, the remote platform may start
+executing the application. Graphene uses Protected FS to transparently decrypt
+the input and the model files using the provisioned key when the PyTorch
+application starts (6). The application then proceeds with execution on
+plaintext files (7). When the PyTorch script is finished, the output file is
+encrypted with the same cryptographic key and saved to the cloud provider's file
+storage (8). At this point, the protected output may be forwarded to the remote
+user who will decrypt it and analyze its contents.
+
+Prerequisites
+-------------
+
+- Ubuntu 18.04. This tutorial should work on other Linux distributions as well,
+ but for simplicity we provide the steps for Ubuntu 18.04 only.
+
+ Please install the following dependencies::
+
+ sudo apt install libnss-mdns libnss-myhostname
+
+- PyTorch (Python3). PyTorch is a framework for machine learning based on
+ Python. Please `install PyTorch `__
+ before you proceed (don't forget to choose Linux as the target OS). We will
+ use Python3 in this tutorial.
+
+- Intel SGX Driver and SDK/PSW. You need a machine that supports Intel SGX and
+ FLC/DCAP. Please follow `this guide
+ `__
+ to install the Intel SGX driver and SDK/PSW. Make sure to install the driver
+ with ECDSA/DCAP attestation.
+
+- Graphene. Follow `Quick Start
+ `__ to build
+ Graphene. In this tutorial, we will use both non-SGX and SGX-backed versions
+ of Graphene. Make sure you build both Graphene loaders (``Runtime/pal-Linux``
+ for non-SGX version and ``Runtime/pal-Linux-SGX`` for SGX version).
+
+Executing Native PyTorch
+------------------------
+
+We start with a very simple example script written in Python3 for PyTorch-based
+ML inferencing. Graphene already provides a minimalistic and *insecure* `PyTorch
+example `__
+which does not have confidentiality guarantees for input/output files and does
+not use remote attestation. In this tutorial, we will use this existing PyTorch
+example as a basis and will improve it to protect all user files.
+
+Go to the directory with Graphene's PyTorch example::
+
+ cd /Examples/pytorch
+
+The directory contains a Python script ``pytorchexample.py`` and other relevant
+files. The script reads a `pretrained AlexNet model
+`__ and an image
+``input.jpg``, and infers the class of an object in the image. Then, the script
+writes the top-5 classification results to a file ``result.txt``.
+
+We first download and save the pre-trained AlexNet model::
+
+ make download_model
+
+This command uses the ``download-pretrained-model.py`` script to download a
+pretrained model and save it as a serialized file ``alexnet-pretrained.pt``.
+See `Saving and Loading Models in PyTorch
+`__ for more
+details.
+
+Now simply run the following command to run PyTorch inferencing::
+
+ python3 pytorchexample.py
+
+This will execute native PyTorch which will write the classification results to
+``result.txt``. The provided example image is a photo of a dog, therefore the
+output file contains "Labrador retriever" as a first result.
+
+In later sections, we will run exactly the same Python script but with Graphene
+and inside SGX enclaves.
+
+Executing PyTorch with Graphene
+-------------------------------
+
+In the next two sections, we will run the exact same PyTorch example with
+Graphene. We will first run PyTorch with non-SGX Graphene (for illustrative
+purposes) and then with SGX-backed Graphene. Note that this part of the tutorial
+still only shows the non-PPML workflow where Graphene doesn't protect
+input/output user files; the end-to-end PPML workflow will be described below.
+
+The porting effort to run PyTorch in Graphene is minimal and boils down to
+creation of the *Graphene PyTorch-specific manifest file*. When Graphene runs
+an executable, it reads a manifest file that describes the execution environment
+including the security posture, environment variables, dynamic libraries,
+arguments, and so on. In the rest of this tutorial, we will create this
+manifest file and explain its options and rationale behind them. Note that the
+manifest file contains both general non-SGX options for Graphene and
+SGX-specific ones. Please refer to `this
+`__ for further
+details about the syntax of Graphene manifests.
+
+Executing PyTorch with non-SGX Graphene
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Let's run the PyTorch example using Graphene, but without an SGX enclave.
+
+Navigate to the PyTorch example directory we examined in the previous section::
+
+ cd /Examples/pytorch
+
+Let's take a look at the template manifest file ``pytorch.manifest.template``.
+For illustrative purposes, we will look at only a few entries of the file. Note
+that we can simply ignore SGX-specific keys (starting with the ``sgx.`` prefix)
+for our non-SGX run.
+
+The executable for the PyTorch workload is ``python3`` (recall that PyTorch is a
+collection of libraries and utilities but it uses Python as an actual
+executable), which is located at the host path ``/usr/bin/python3``::
+
+ loader.exec = file:/usr/bin/python3
+
+Notice that the manifest file is not fully secure because it propagates
+untrusted command-line arguments and environment variables into the enclave. We
+keep these work-arounds in this tutorial for simplicity, but this configuration
+must not be used in production::
+
+ loader.insecure__use_cmdline_argv = 1
+ loader.insecure__use_host_env = 1
+
+We mount the entire ``/Runtime/`` host-level directory to
+the ``/lib`` directory seen inside Graphene. This trick allows to transparently
+replace standard C libraries with Graphene-patched libraries::
+
+ fs.mount.lib.type = chroot
+ fs.mount.lib.path = /lib
+ fs.mount.lib.uri = file:$(GRAPHENEDIR)/Runtime/
+
+We also mount other directories such as ``/usr``, ``/etc``, and ``/tmp``
+required by Python and PyTorch (they search for libraries and utility files in
+these system directories).
+
+Finally, we mount the path containing the Python packages installed via pip::
+
+ fs.mount.pip.type = chroot
+ fs.mount.pip.path = $(HOME)/.local/lib
+ fs.mount.pip.uri = file:$(HOME)/.local/lib
+
+Now we can run ``make`` to build/copy all required Graphene files::
+
+ make
+
+This command will autogenerate a couple new files:
+
+#. Generate the actual non-SGX Graphene manifest (``pytorch.manifest``) from the
+ template manifest file. This file will be used by Graphene to decide on
+ different manifest options how to execute PyTorch inside Graphene.
+
+#. Create a symbolic link to the generic Graphene loader (``pal_loader``). This
+ is just for convenience.
+
+Now, launch Graphene with the generated manifest via ``pal_loader``. You can
+simply append the arguments after the manifest name. Our example takes
+``pytorchexample.py`` as an argument::
+
+ ./pal_loader pytorch.manifest pytorchexample.py
+
+That's it. You have run the PyTorch example with Graphene. You can check
+``result.txt`` to make sure it ran correctly.
+
+Executing PyTorch with Graphene in SGX Enclave
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+In this section, we will learn how to use Graphene to run the same PyTorch
+example inside an Intel SGX enclave. Let's go back to the manifest template
+(recall that the manifest keys starting with ``sgx.`` are SGX-specific syntax;
+these entries are ignored if Graphene runs in non-SGX mode).
+
+Below, we will highlight some of the SGX-specific manifest options in
+``pytorch.manifest.template``. SGX syntax is fully described `here
+`__.
+
+First, here are the following SGX-specific lines in the manifest template::
+
+ sgx.trusted_files.ld = file:$(GRAPHENEDIR)/Runtime/ld-linux-x86-64.so.2
+ sgx.trusted_files.libc = file:$(GRAPHENEDIR)/Runtime/libc.so.6
+ ...
+
+``sgx.trusted_files.`` specifies a file that will be verified and trusted
+by the SGX enclave. Note that the key string ```` may be an arbitrary
+legal string (but without ``-`` and other special symbols) and does not have to
+be the same as the actual file name.
+
+The way these Trusted Files work is before Graphene runs PyTorch inside the SGX
+enclave, Graphene generates the final SGX manifest file using ``pal-sgx-sign``
+Graphene utility. This utility calculates hashes of each trusted file and
+appends them as ``sgx.trusted_checksum.`` to the final SGX manifest. When
+running PyTorch with SGX, Graphene reads trusted files, finds their
+corresponding trusted checksums, and compares the calculated-at-runtime checksum
+against the expected value in the manifest.
+
+The PyTorch manifest template also contains ``sgx.allowed_files.``
+entries. They specify files unconditionally allowed by the enclave::
+
+ sgx.allowed_files.pythonhome = file:$(HOME)/.local/lib
+
+This line unconditionally allows all Python libraries in the path to be loaded
+into the enclave. Ideally, the developer needs to replace it with
+``sgx.trusted_files`` for each of the dependent Python libraries.
+
+Allowed files are *not* cryptographically hashed and verified. Thus, this is
+*insecure* and discouraged for production use (unless you are sure that the
+contents of the files are irrelevant to security of your workload). Here, we use
+these allowed files only for simplicity. A next tutorial on PyTorch (with Docker
+integration) replaces all allowed files with trusted/protected files (that
+tutorial is work in progress).
+
+There is also the following line in the manifest template::
+
+ sgx.allow_file_creation = 1
+
+This allows the enclave to generate new files. We need this since the PyTorch
+Python script writes the result to ``result.txt``.
+
+Now we desribed how the manifest template looks like and what the SGX-specific
+manifest entries represent. Let's prepare all the files needed to run PyTorch in
+an SGX enclave::
+
+ make SGX=1
+
+The above command performs the following tasks:
+
+#. Generates the final SGX manifest file ``pytorch.manifest.sgx``.
+
+#. Signs the manifest and generates the SGX signature file containing SIGSTRUCT
+ (``pytorch.sig``).
+
+#. Creates a dummy EINITTOKEN token file ``pytorch.token`` (this file is used
+ for backwards compatibility with SGX platforms with EPID and without Flexible
+ Launch Control).
+
+After running this command and building all the required files, we can simply
+set ``SGX=1`` environment variable and use ``pal_loader`` to launch the PyTorch
+workload inside an SGX enclave::
+
+ SGX=1 ./pal_loader pytorch.manifest.sgx pytorchexample.py
+
+It will run exactly the same Python script but inside the SGX enclave. Again,
+you can verify that PyTorch ran correctly by examining ``result.txt``.
+
+End-To-End Confidential PyTorch Workflow
+----------------------------------------
+
+Background on Remote Attestation, RA-TLS and Secret Provisioning
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Intel SGX provides a way for the SGX enclave to attest itself to the remote
+user. This way the user gains trust in the SGX enclave running in an untrusted
+environment, ships the application code and data, and is sure that the *correct*
+application was executed inside a *genuine* SGX enclave. This process of gaining
+trust in a remote SGX machine is called Remote Attestation (RA).
+
+Graphene has two features that transparently add SGX RA to the application: (1)
+RA-TLS augments normal SSL/TLS sessions with an SGX-specific handshake callback,
+and (2) Secret Provisioning establishes a secure SSL/TLS session between the SGX
+enclave and the remote user so that the user may gain trust in the remote
+enclave and provision secrets to it. Secret Provisioning builds on top of RA-TLS
+and typically runs before the application. Both features are provided as opt-in
+libraries.
+
+The Secret Provisioning library provides a simple non-programmatic API to
+applications: it transparently initializes the environment variable
+``SECRET_PROVISION_SECRET_STRING`` with a secret obtained from the remote user
+during remote attestation. In our PyTorch example, the provisioned secret is the
+confidential (master, or wrap) key to encrypt/decrypt user files. To inform
+Graphene that the obtained secret is indeed the key for file encryption, it is
+enough to set the environment variable ``SECRET_PROVISION_SET_PF_KEY``.
+
+Note that RA-TLS and Secret Provisioning work both with the EPID-based and the
+ECDSA/DCAP schemes of SGX remote attestation. Since this tutorial concentrates
+on an untrusted-cloud scenario, we use the ECDSA/DCAP attestation framework.
+
+Background on Protected Files
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Graphene provides a feature of `Protected Files
+`__,
+which encrypts files and transparently decrypts them when the application reads
+or writes them. Integrity- or confidentiality-sensitive files (or whole
+directories) accessed by the application must be marked as protected files in
+the Graphene manifest. New files created in a protected directory are
+automatically treated as protected. The encryption format used for protected
+files is borrowed from the similar feature of Intel SGX SDK.
+
+This feature can be combined with Secret Provisioning such that the files are
+encrypted/decrypted using the provisioned wrap key, as explained in the previous
+section.
+
+Preparing Confidential PyTorch Example
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+In this section, we will transform our native PyTorch application into an
+end-to-end confidential application. We will encrypt all user files before
+starting the enclave, mark them as protected, let the enclave communicate with
+the secret provisioning server to get attested and receive the master wrap key
+for encryption and decryption of protected files, and finally run the actual
+PyTorch inference.
+
+We will use the previous non-confidential PyTorch example as a starting point,
+so copy the entire PyTorch directory::
+
+ cd /Examples
+ cp -R pytorch pytorch-confidential
+
+We will also use the reference implementation of Secret Provisioning found under
+``Examples/ra-tls-secret-prov`` directory, so build and copy all the relevant
+files from there::
+
+ cd /Examples/ra-tls-secret-prov
+ make -C ../../Pal/src/host/Linux-SGX/tools/ra-tls dcap
+ make dcap pf_crypt
+
+The second line in the above snippet creates Graphene-specific DCAP libraries
+for preparation and verification of SGX quotes (needed for SGX remote
+attestation). The last line builds the required DCAP binaries and copies
+relevant Graphene utilities such as ``pf_crypt`` to encrypt input files.
+
+The last line also builds the secret provisioning server
+``secret_prov_server_dcap``. We will use this server to provision the master
+wrap key (used to encrypt/decrypt protected input and output files) to the
+PyTorch enclave. See `Secret Provisioning Minimal Examples
+`__
+for more information.
+
+Preparing Input Files
+^^^^^^^^^^^^^^^^^^^^^
+
+The user must encrypt all input files: ``input.jpg``, ``classes.txt``, and
+``alexnet-pretrained.pt``. For simplicity, we re-use the already-existing stuff
+from the ``Examples/ra-tls-secret-prov`` directory. In particular, we re-use
+the confidential wrap key::
+
+ cd /Examples/pytorch-confidential
+ mkdir files
+ cp ../ra-tls-secret-prov/files/wrap-key files/
+
+In real deployments, the user must replace this ``wrap-key`` with her own
+128-bit encryption key.
+
+We also re-use the ``pf_crypt`` utility (with its ``libsgx_util.so`` helper
+library and required mbedTLS libraries) that encrypts/decrypts the files::
+
+ cp ../ra-tls-secret-prov/libsgx_util.so .
+ cp ../ra-tls-secret-prov/libmbed*.so* .
+ cp ../ra-tls-secret-prov/pf_crypt .
+
+Let's also make sure that ``alexnet-pretrained.pt`` network-model file exists
+under our new directory::
+
+ make download_model
+
+Now let's encrypt the original plaintext files. We first move these files under
+the ``plaintext/`` directory and then encrypt them using the wrap key::
+
+ mkdir plaintext/
+ mv input.jpg classes.txt alexnet-pretrained.pt plaintext/
+
+ LD_LIBRARY_PATH=. ./pf_crypt encrypt -w files/wrap-key -i plaintext/input.jpg -o input.jpg
+ LD_LIBRARY_PATH=. ./pf_crypt encrypt -w files/wrap-key -i plaintext/classes.txt -o classes.txt
+ LD_LIBRARY_PATH=. ./pf_crypt encrypt -w files/wrap-key -i plaintext/alexnet-pretrained.pt -o alexnet-pretrained.pt
+
+You can verify now that the input files are encrypted. In real deployments,
+these files must be shipped to the remote untrusted cloud.
+
+Preparing Secret Provisioning
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The user must prepare the secret provisioning server and start it. For this,
+copy the secret provisioning executable and its helper library from
+``Examples/ra-tls-secret-prov`` to the current directory::
+
+ cp ../ra-tls-secret-prov/libsecret_prov_verify_dcap.so .
+ cp ../ra-tls-secret-prov/secret_prov_server_dcap .
+
+Also, copy the server-identifying certificates so that in-Graphene secret
+provisioning library can verify the provisioning server (via classical X.509
+PKI)::
+
+ cp -R ../ra-tls-secret-prov/certs ./
+
+These certificates are dummy mbedTLS-provided certificates; in production, you
+would want to generate real certificates for your secret-provisioning server and
+use them.
+
+Now we can launch the secret provisioning server::
+
+ ./secret_prov_server_dcap &
+
+In this tutorial, we simply run it locally (``localhost:4433`` as configured in
+the manifest) for simplicity. In reality, the user must run it on a trusted
+remote machine. In that case, ``loader.env.SECRET_PROVISION_SERVERS`` in the
+manifest (see below) must point to the address of the remote-user machine. We
+launch the server in the background.
+
+Preparing Manifest File
+^^^^^^^^^^^^^^^^^^^^^^^
+
+Finally, let's modify the manifest file. Open ``pytorch.manifest.template``
+with your favorite text editor.
+
+Replace ``trusted_files`` with ``protected_files`` for the input files::
+
+ # sgx.trusted_files.classes = file:classes.txt
+ sgx.protected_files.classes = file:classes.txt
+
+ # sgx.trusted_files.image = file:input.jpg
+ sgx.protected_files.image = file:input.jpg
+
+ # sgx.trusted_files.model = file:alexnet-pretrained.pt
+ sgx.protected_files.model = file:alexnet-pretrained.pt
+
+Also add ``result.txt`` as a protected file so that PyTorch writes the
+*encrypted* result into it::
+
+ sgx.protected_files.result = file:result.txt
+
+Now, let's add the secret provisioning library to the manifest. Append the
+current directory ``./`` to ``LD_LIBRARY_PATH`` so that PyTorch and Graphene
+add-ons search for libraries in the current directory::
+
+ # this instructs in-Graphene dynamic loader to search for dependencies in the current directory
+ loader.env.LD_LIBRARY_PATH = /lib:/usr/lib:$(ARCH_LIBDIR):/usr/$(ARCH_LIBDIR):./
+
+Add the following lines to enable remote secret provisioning and allow protected
+files to be transparently decrypted by the provisioned key. Recall that we
+launched the secret provisioning server locally on the same machine, so we
+re-use the same ``certs/`` directory and specify ``localhost``. For more info on
+the used environment variables and other manifest options, see `here
+`__::
+
+ sgx.remote_attestation = 1
+
+ loader.env.LD_PRELOAD = libsecret_prov_attest.so
+ loader.env.SECRET_PROVISION_CONSTRUCTOR = 1
+ loader.env.SECRET_PROVISION_SET_PF_KEY = 1
+ loader.env.SECRET_PROVISION_CA_CHAIN_PATH = "certs/test-ca-sha256.crt"
+ loader.env.SECRET_PROVISION_SERVERS = "localhost:4433"
+
+ sgx.trusted_files.libsecretprovattest = file:libsecret_prov_attest.so
+ sgx.trusted_files.cachain = file:certs/test-ca-sha256.crt
+
+The ``libsecret_prov_attest.so`` library provides the in-enclave logic to attest
+the SGX enclave, Graphene instance, and the application running in it to the
+remote secret-provisioning server. Graphene needs to locate this library, so
+let's copy it to our working directory::
+
+ cp ../ra-tls-secret-prov/libsecret_prov_attest.so ./
+
+Building and Executing End-To-End PyTorch Example
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Now that we prepared the files and the manifest, let's re-generate the manifest
+files, tokens, and signatures::
+
+ make clean
+ make SGX=1
+
+It is also important to remove the file ``result.txt`` if it exists. Otherwise
+the Protected FS will detect the already-existing file and fail. So let's remove
+it unconditionally::
+
+ rm -f result.txt
+
+We are ready to run the end-to-end PyTorch example. Notice that we didn't change
+a line of code in the Python script. Moreover, we can run it with exactly the
+same command used in the previous section::
+
+ SGX=1 ./pal_loader pytorch.manifest pytorchexample.py
+
+This should run PyTorch with encrypted input files and generate the encrypted
+``result.txt`` output file. Note that we already launched the secret
+provisioning server on the same machine, so secret provisioning will run
+locally.
+
+Decrypting Output File
+^^^^^^^^^^^^^^^^^^^^^^
+
+After our protected PyTorch inference is finished, you'll see ``result.txt`` in
+the directory. This file is encrypted with the same key as was used for
+encryption of input files. In order to decrypt it, use the following command::
+
+ LD_LIBRARY_PATH=. ./pf_crypt decrypt -w files/wrap-key -i result.txt -o plaintext/result.txt
+
+You can check the result written in ``plaintext/result.txt``. It must be the
+same as in our previous runs.
+
+Cleaning Up
+^^^^^^^^^^^
+
+When done, don't forget to terminate the secret provisioning server::
+
+ killall secret_prov_server_dcap
diff --git a/Examples/pytorch/Makefile b/Examples/pytorch/Makefile
index f2be2a8f..a58d32b8 100644
--- a/Examples/pytorch/Makefile
+++ b/Examples/pytorch/Makefile
@@ -31,7 +31,7 @@ endif
UBUNTU_VERSION = $(DISTRIB_ID)$(DISTRIB_RELEASE)
.PHONY: all
-all: pytorch.manifest python3.manifest pal_loader alexnet-pretrained.pt
+all: pytorch.manifest python3.manifest pal_loader
ifeq ($(SGX),1)
all: pytorch.token python3.token
endif
@@ -65,7 +65,7 @@ python3.manifest: pytorch.manifest
cp $< $@
sed -i '/trusted_children/d' $@
-python3.sig: python3.manifest alexnet-pretrained.pt
+python3.sig: python3.manifest
$(GRAPHENEDIR)/Pal/src/host/Linux-SGX/signer/pal-sgx-sign \
-libpal $(GRAPHENEDIR)/Runtime/libpal-Linux-SGX.so \
-key $(SGX_SIGNER_KEY) \
@@ -74,15 +74,14 @@ python3.sig: python3.manifest alexnet-pretrained.pt
pal_loader:
ln -s $(GRAPHENEDIR)/Runtime/pal_loader $@
-alexnet-pretrained.pt: download_model
-
.PHONY: download_model
download_model:
$(PYTHON3) download-pretrained-model.py
.PHONY: clean
clean:
- $(RM) *.token *.sig *.manifest.sgx *.manifest pal_loader *.pt result.txt
+ $(RM) *.token *.sig *.manifest.sgx *.manifest pal_loader
.PHONY: distclean
distclean: clean
+ $(RM) *.pt result.txt
diff --git a/Examples/pytorch/README.md b/Examples/pytorch/README.md
index 9c50f574..6b239ef4 100644
--- a/Examples/pytorch/README.md
+++ b/Examples/pytorch/README.md
@@ -19,7 +19,8 @@ With high probability (41%) the classifier detected the image to contain a Labra
The following steps should suffice to run the workload on a stock Ubuntu 16.04 and 18.04
installation.
-- `sudo apt-get install python3-pip lsb-release` to install `pip` and `lsb_release`. The former is
+- `sudo apt install libnss-mdns libnss-myhostname` to install additional DNS-resolver libraries.
+- `sudo apt install python3-pip lsb-release` to install `pip` and `lsb_release`. The former is
required to install additional Python packages while the latter is used by the Makefile.
- `pip3 install --user torchvision pillow` to install the torchvision and pillow Python packages and
their dependencies (usually in $HOME/.local). WARNING: This downloads several hundred megabytes of
diff --git a/Examples/pytorch/pytorch.manifest.template b/Examples/pytorch/pytorch.manifest.template
index 5d4527f5..e21ef733 100644
--- a/Examples/pytorch/pytorch.manifest.template
+++ b/Examples/pytorch/pytorch.manifest.template
@@ -20,6 +20,8 @@ loader.insecure__use_cmdline_argv = 1
# Propagate environment variables from the host. Don't use this on production!
loader.insecure__use_host_env = 1
+
+# Overwrite some environment variables
loader.env.LD_LIBRARY_PATH = /lib:/usr/lib:$(ARCH_LIBDIR):/usr/$(ARCH_LIBDIR)
# Default glibc files, mounted from the Runtime directory in GRAPHENEDIR
@@ -86,6 +88,7 @@ sgx.trusted_files.libpthread = file:$(GRAPHENEDIR)/Runtime/libpthread.so.0
sgx.trusted_files.libresolv = file:$(GRAPHENEDIR)/Runtime/libresolv.so.2
sgx.trusted_files.librt = file:$(GRAPHENEDIR)/Runtime/librt.so.1
sgx.trusted_files.libutil = file:$(GRAPHENEDIR)/Runtime/libutil.so.1
+sgx.trusted_files.libnssdns = file:$(GRAPHENEDIR)/Runtime/libnss_dns.so.2
sgx.trusted_files.libstdc = file:/usr/$(ARCH_LIBDIR)/libstdc++.so.6
sgx.trusted_files.libgccs = file:$(ARCH_LIBDIR)/libgcc_s.so.1
@@ -108,10 +111,12 @@ sgx.trusted_files.libmpdec = file:/usr/$(ARCH_LIBDIR)/libmpdec.so.2
# Ubuntu18.04 sgx.trusted_files.libssl = file:/usr/$(ARCH_LIBDIR)/libssl.so.1.1
# Name Service Switch (NSS) libraries (Glibc dependencies)
-sgx.trusted_files.libnssfiles = file:$(ARCH_LIBDIR)/libnss_files.so.2
-sgx.trusted_files.libnsscompat = file:$(ARCH_LIBDIR)/libnss_compat.so.2
-sgx.trusted_files.libnssnis = file:$(ARCH_LIBDIR)/libnss_nis.so.2
-sgx.trusted_files.libnsl = file:$(ARCH_LIBDIR)/libnsl.so.1
+sgx.trusted_files.libnssfiles = file:$(ARCH_LIBDIR)/libnss_files.so.2
+sgx.trusted_files.libnsscompat = file:$(ARCH_LIBDIR)/libnss_compat.so.2
+sgx.trusted_files.libnssnis = file:$(ARCH_LIBDIR)/libnss_nis.so.2
+sgx.trusted_files.libnsl = file:$(ARCH_LIBDIR)/libnsl.so.1
+sgx.trusted_files.libnssmyhostname = file:$(ARCH_LIBDIR)/libnss_myhostname.so.2
+sgx.trusted_files.libnssmdns = file:$(ARCH_LIBDIR)/libnss_mdns4_minimal.so.2
# The script to run
sgx.trusted_files.script = file:pytorchexample.py
@@ -146,16 +151,24 @@ sgx.allowed_files.python3 = file:/usr/lib/python3
sgx.allowed_files.pythonhome = file:$(HOME)/.local/lib
# Ubuntu16.04 sgx.allowed_files.python35 = file:/usr/lib/python3.5
# Ubuntu18.04 sgx.allowed_files.python36 = file:/usr/lib/python3.6
+# Ubuntu16.04 sgx.allowed_files.python35local = file:/usr/local/lib/python3.5
+# Ubuntu18.04 sgx.allowed_files.python36local = file:/usr/local/lib/python3.6
-# Some Python package wants to access these files on Ubuntu 16.04
-# Ubuntu16.04 sgx.allowed_files.aptconfd = file:/etc/apt/apt.conf.d
-# Ubuntu16.04 sgx.allowed_files.aptconf = file:/etc/apt/apt.conf
-# Ubuntu16.04 sgx.allowed_files.apport = file:/etc/default/apport
+# APT config files
+sgx.allowed_files.aptconfd = file:/etc/apt/apt.conf.d
+sgx.allowed_files.aptconf = file:/etc/apt/apt.conf
+sgx.allowed_files.apport = file:/etc/default/apport
# Name Service Switch (NSS) files (Glibc reads these files)
-sgx.trusted_files.nsswitch = file:/etc/nsswitch.conf
-sgx.trusted_files.group = file:/etc/group
-sgx.trusted_files.passwd = file:/etc/passwd
+sgx.allowed_files.nsswitch = file:/etc/nsswitch.conf
+sgx.allowed_files.group = file:/etc/group
+sgx.allowed_files.passwd = file:/etc/passwd
+
+# DNS hostname resolution files (Glibc reads these files)
+sgx.allowed_files.hostconf = file:/etc/host.conf
+sgx.allowed_files.hosts = file:/etc/hosts
+sgx.allowed_files.gaiconf = file:/etc/gai.conf
+sgx.allowed_files.resolv = file:/etc/resolv.conf
# Graphene optionally provides patched OpenMP runtime library that runs faster
# inside SGX enclaves (execute `make -C LibOS gcc` to generate it). Uncomment