bloomberg/ample

AMPLE: Fine-grained File Access Policies for Server Applications

C++

0

1 commits

updated Nov 17, 2025

See the code

README

AMPLE: Fine-grained File Access Policies for Server Applications

[!NOTE] We are publishing this tool as open source solely for the purpose of reproducibility of the data in our paper. We will not be providing ongoing maintenance or support for this project.

About The Project

AMPLE builds AppArmor MAC policies for server applications by combining static and dynamic analysis. AppArmor policies can restrict a process from accessing irrelavent (and possibly security-critical) IPC resources. In our current version, AMPLE builds policies to restrict access to files.

To this end, AMPLE first analyzes a program statically, extracting any file paths available in the source code. It then identifies and instruments initialization instructions for those that are only available at runtime. Given that many of the files required by server applications are only specified at runtime (e.g., path to the configuration file specified through a commandline option), AMPLE uses these instrumentations to extract their value at runtime.

To summarize, AMPLE performs the following steps to analyze a program:

  1. Statically analyzes the program through an LLVM pass and instruments the binary
  2. Launches the instrumented binary to extract runtime available file paths
  3. Combines statically extracted file paths with those available at runtime to build the final policy
  4. Validates syntax correctness of the generated policy by enforcing with AppArmor
  5. If the policy passes the syntax correctness, AMPLE attempts to run the program with the policy being enforced to validate the policy's soundness.

We have developed a script that takes care of invoking all the necessary scripts that perform the steps above (discussed further below).

LLVM Pass

We use an LLVM pass to analyze and instrument the program. Our pass is built on top of SVF. It can be used both as a standalone program or with the opt toolkit. However, to utilize AMPLE's runtime analysis, we must instrument the program, which requires running it through the opt toolkit.

Furthermore, our analysis depends on an SVF pointer analysis to generate sound results. Although our implementation allows running AMPLE without enabling SVF, doing so makes the results unsound.

This is due to the following reasons:

  1. We won't have the indirect function call targets resolved, so performing the interprocedural data flow analysis will not be sound.
  2. Our multiphase analysis -- which identifies whether an API call or its value's initialization happen only during the initialization phase -- relies on having the results of pointer analysis for the indirect call targets. Therefore, AMPLE will disable the multiphase analysis if/when pointer analysis is not enabled.
  3. Our nested-type analysis relies on memory object types to extract their values. If the address of any of these types are passed through a function, it could be used through another type, making our nested-based analysis incorrect. Therefore, we must perform pointer analysis for these cases.

NOTE If the provided script (runAll.sh) is used, all the required options will be used when running our analysis, and the issues mentioned above will be addressed.

Python Script

The LLVM pass generates an initial policy that requires bootstrapping to be converted to the final policy. We have created a Python script that takes the LLVM pass output as its input and combines it with runtime file paths extracted by running the instrumented binary to generate a final policy. This is the policy that is used to enforce AppArmor for the program.

Getting Started

Prerequisites

If you choose to build our pass on your own system, the following prerequisites must be met:

  • Build gold linker (binutils)
  • Build LLVM 12
  • Build SVF (use the SVF provided in our repoository, since we have made modifications to it)
  • Install packages: sudo apt install python3 apparmor apparmor-utils

NOTE: We have used SVF v2.2 in our repository, which is compatible with LLVM v12. As of October 2025, the most recent SVF version is 3.1 which is compatible with LLVM v16. If you need to run AMPLE with a newer LLVM version, then you must modify both our AMPLE LLVM pass and SVF to make it compatible with the newer LLVM version.

Build binutils-2.42

To build binutils-2.42 from the source code, we must follow these steps:

wget https://ftp.gnu.org/gnu/binutils/binutils-2.42.tar.xz
tar -xvf binutils-2.42.tar.xz
mkdir binutils.build
../binutils-2.42/configure --enable-gold --enable-plugins --disable-werror
make all-gold

Build LLVM 12

To build LLVM 12 from the source code with the Gold plugin support, we must follow these steps (taken from the SVF repository):

Then run the following commands:

tar xf llvm-12.0.0.src.tar.xz
tar xf clang-12.0.0.src.tar.xz
mv clang-12.0.0.src llvm-12.0.0.src/tools/clang

Create a build directory and then run the following:

mkdir llvm-12.0.0.obj
cd llvm-12.0.0.obj
cmake -DLLVM_BINUTILS_INCDIR=/huge/hamed/tools/binutils-2.42/include/ -DCMAKE_BUILD_TYPE=Release ../llvm-12.0.0.src (or add "-DCMAKE_BUILD_TYPE:STRING=Debug" for debug version)
make -j8

Create a setenv.sh script that sets paths for LLVM and SVF in the required environment variables:

export LLVM_SRC=your_path_to_llvm-12.0.0.src
export LLVM_OBJ=your_path_to_llvm-12.0.0.obj
export LLVM_DIR=your_path_to_llvm-12.0.0.obj
export PATH=$LLVM_DIR/bin:$PATH
export SVF_DIR=[AMPLE_ROOT]/SVF-2.2

Set the environment variables before proceeding.

chmod +x setenv.sh
source setenv.sh

Build SVF

To build SVF you need to create build directory, set up the build scripts using cmake and then build using make.

# Under ample/SVF-2.2:
mkdir Release-build
cd Release-build

cmake .. # If no luck with default compiler, CC=clang CXX=clang++ cmake ..
make -j8

Installation

Build AMPLE Pass

After building LLVM and SVF and adding the paths to the PATH environment variable, we can build the project by creating a build folder and using CMake.

  1. Clone the repository

    git clone https://github.com/bloomberg/ample.git
    
  2. Build the LLVM pass

    cd [AMPLE_ROOT]/src/c++/syscall-arg-analyzer;
    mkdir build
    cd build
    CC=clang CXX=clang++ cmake ../ (add -DCMAKE_BUILD_TYPE:STRING=Debug for debug build)
    
  3. Run the pass

    ./bin/ample
    

Please keep in mind that we need to use the LLVM-provided opt to run the analysis if we want to instrument the binary to extract runtime values. By running AMPLE itself, you can only perform static analysis and identify how many values can be (potentially) extracted at runtime. The runtime values themselves need to be extracted by running the instrumented program.

Fix Default Paths

The scripts which run the analysis rely on certain environment-specific variables to find the paths to the compiled AMPLE pass and other scripts they depend upon. The paths required by this script are the following:

export AMPLE_HOME="[path_to_ample]/ample/" # Specifies path to root of ample repo
export AMPLE_BUILD="[path_to_ample]/ample/src/c++/syscall-arg-analyzer/Debug-build.chain-overhaul" # Specifies path to build of ample
export SVF_DIR="[path_to_ample]/ample/SVF-2.2/"  # Path to SVF src directory
export LLVM_DIR="[path_to_llvm]/llvm-12.0.0.obj/"      # Path to LLVM build directory
export APIARG_DIR="$AMPLE_HOME/data/"   # Path to folder which contains file with api-arg pairs we want to extract values for

These variable are set through a setenv.sh file located in [AMPLE_ROOT]/src/c++/syscall-arg-analyzer. Assuming that the LLVM_DIR and SVF_DIR have been set above you only need to change the AMPLE_HOME and AMPLE_BUILD variables.

AMPLE_HOME: This variable should contain the path to the root AMPLE directory. This is the main repository folder on your local system.

AMPLE_BUILD: This variable should contain the path to the local AMPLE build directory.

Please modify these paths before proceeding to the next steps.

Generate Program's Bitcode

Our analysis takes the LLVM IR as its input. Therefore, to run our analysis on an application or library, you need to first generate its bitcode. We have provided a guide on how to do that.

AMPLE expects the developer to provide the transition point between the initialization and processing phases for its multi-phase analysis. Through its multi-phase analysis, AMPLE can determine whether a file path is initialized during the program startup or in its processing phase. To do so, you must annotate the source code; explanation here.

Also, to add instrumentations for extracting runtime-available file path values, we must link the target program's bitcode with our instrumentation functions. These functions are then invoked when an initialization instruction is identified. We have created a script for adding these instrumentations to each program:

cd [AMPLE_ROOT]/src/c++/syscall-arg-analyzer/instrumentation
clang -emit-llvm -c ample_instrument.c -o ample_instrument.bc
./instrument-all.sh [folder_with_original_bitcodes] ample_instrument.bc $AMPLE_HOME/bc.outputs [appname]

The [appname] in this command is optional and can be used to run the script for only one program.

NOTE: This script only adds the instrumentation functions to the program bitcode. We must run the LLVM pass to identify the instructions that must be instrumented and add function calls to those instrumentation functions.

Usage

We have created a runAll.sh script to run all the steps needed to generate an AppArmor policy using AMPLE. The runAll.sh script which is in the src/c++/syscall-arg-analyzer folder takes care of all the options that need to be passed to AMPLE.

NOTE: After generating an instrumented LLVM bitcode, AMPLE compiles the bitcode into the final binary. For this step, we must have installed any libraries that the program depends upon so that it can be linked with the program. The dependencies for all the programs in our dataset are as follows:

sudo apt install libssl-dev libpcre2-dev libpcre2-8-0 libpam0g-dev libfastjson-dev uuid-dev libevent-dev libxml2-dev libcap-dev libdb-dev zlib1g-dev

NOTE: AMPLE needs to run the target application to extract the runtime-based file paths. Therefore, each application must have a valid configuration file with which it can be run. Valid configuration files have been provided for the applications in our test set. However, in case the runtime setting of one of these applications conflicts with another program running on the target system, it may lead to invalid policy generation. It is critical to go through and check these issues before running AMPLE for an application. For example, based on our provided configuration file for Nginx, it will run on port 8080. Therefore, if another program is running on this port, you need to modify Nginx's port before running AMPLE. The configuration files for all applications can be found in [AMPLE_HOME]/app_run_env.

NOTE: Some programs have other environment-related properties in their configuration file that need to be modified before being executed. The settings have been listed below: - Lighttpd: The lighttpd.conf file found in our app_run_env folder specifies the user and group to run lighttpd with. You must change the user and group defined there to a valid user and group on your system. Furthermore, you need to change the same properties which are also used in the setup_run_env.sh script of lighttpd. This file can be found in [AMPLE_ROOT]/app_run_env/lighttpd.

To run the script we use the following command:

./runAll.sh [appname] instrument validate

Instead of [appname], you can either specify the application name for which you would like to run the analysis or use 'all' to run it for all of the apps in our dataset. The instrument option is optional, it specifies whether an instrumented bitcode should be generated or only the static analysis portion should be run. If the instrument option is passed, you can also pass the validate option to validate the generated policy by running the program.

Examples:

./runAll.sh memcached

This would run the analysis for memcached and not build an instrumented binary. The generated policy would only contains paths derived statically.

./runAll.sh memcached instrument

This would run the analysis for memcached, but also create an instrumented binary. Even though the instrumented binary is generated, you stil need to pass the validate option for the script to run the program and extract runtime-available file paths as well.

./runAll.sh memcached instrument validate

This command would run all the steps and generate an AppArmor policy which contains both statically derived and dynamically derived policies.

The applications currently available (when using runAll.sh the names should have the same syntax as specified below):

  • memcached
  • redis
  • nginx
  • exim
  • lighttpd
  • snmpd
  • proftpd
  • smtpd
  • monkey
  • named

By default, the following paths will be used to store the output of the analysis:

  • $AMPLE_HOME/logs: full log of running the analysis will be stored separately for each application in this folder (named by the app)
  • $AMPLE_HOME/policies: the statically derived policy, and the final policy will be stored in two separate policy files in this folder
  • $AMPLE_HOME/bc.outputs: if the instrument option is specified the instrumented bitcode will be stored in this path
  • $AMPLE_HOME/bin.outputs: if the instrument option is specified the instrumented binary will be stored in this path

Further Details

The Python script and its respective files are located in [AMPLE_ROOT]/src/python. To use the Python script, we first need to provide its settings through the config.json file available in this folder. These settings include general options that are the same for all programs across the same OS, and application-specific options that are specified for each program separately.

The general-purpose options include: enforce-cmd and disable-cmd which specify how to enable and disable an AppArmor policy (this might change in different OS versions). Another option is policy-folder which is the default path to store the generated policies.

The next option is libc-required-libraries. The standard C library (glibc) can dynamically load other libraries. Since we do not analyze glibc statically --- because it cannot be compiled with LLVM --- we expect these libraries to be provided manually. This should not change regularly. However, if it does, these library names or paths should be specified here. If the library name pattern is provided, our script will attempt to find a library file matching this name and add its full path. If the path is provided, AMPLE will add that path as is to the final policy.

We also have application-specific options which are configured in the apps section of the JSON file. We have already provided the settings needed for the 10 applications in our dataset. You must configure these settings when adding a new program to the dataset. You can override some of these options through the commandline when invoking the Python script. Each application has six main properties which should be configured, however, if you use the runAll.sh script to run AMPLE, it will override all the file paths specified in this JSON file when invoking this Python script, so the correct paths are passed to the script. Therefore, there is no need to modify these paths in the JSON file.

"memcached": {
    "llvm-policy-path": "[llvm-policy-path]",
    "format" : "apparmor",
    "binary-path": "/home/ssm-user/analysis/apps/memcached-1.6.9/memcached",
    "runtime-paths-auto": "/tmp/ample/memcached.instrumented",
    "runtime-paths-manual": {
    },
    "caps-manual": [
        "setgid",
        "setuid"
    ]
},

llvm-policy-path: path to the policy built by the LLVM pass --- no need to change if the Python script is called through runAll.sh

format: for now we only support AppArmor policies, but it can be extended to support SELinux

binary-path: path to the binary of the target application or library. This is required to extract the libraries linked to it --- no need to change if the Python script is called through runAll.sh

runtime-paths-auto: the path to the files where we store runtime-extracted file path values --- no need to change if the Python script is called through runAll.sh

runtime-paths-manual: a dictionary with paths specified at runtime along with their permissions.

caps-manual: AppArmor policies can restrict the capabilities used by a program as well. Our current AMPLE design does not support automatically extracting program's required capabilities, so we must add the program's needed capabilities here.

This script will be run when using the runAll.sh script.

The runAll.sh Script

The runAll.sh script contains all the options and commands to run AMPLE for a program. In this section we have provided details on this script and its parameters.

Since runAll.sh executes both the LLVM pass and the Python script, we have defined the required command line options for each of these analyses in this script.

LLVM Pass Options

We will first describe the LLVM pass command line options that determine which optional features of the pass should be executed. (To see a full list of all the available runtime options for AMPLE's LLVM pass we can use: [AMPLE_BUILD_DIR]/ample --help | grep AMPLE) These options are defined in the following variable names: AMPLEOPTS: The final options used to run the LLVM pass.

GENERALOPTS: These are the LLVM pass options that we are currently using when analyzing any program. At some point, we could specify these values in the LLVM pass source as the default values and refrain from repeating them here.

TYPEOPT: Our pass uses type-based argument filtering for reducing the spurious indirect function call edges. This was used in the Temporal System Call Specialization paper. We do not add this option in the general options (GENERALOPTS), because it can lead to soundness issues for programs developed with C++.

INSTRUMENTOPT: This variable adds the -enable-instrument option which can only be used if the pass is run through opt. It enables the instrumentation part of the program.

PTSLOOP: This variable adds the -pts-based-depth option which is an experimental feature that is not fully tested yet.

These are the options that are defined the same across all programs. We also have program-specific options that are defined in the if clauses in the script. The program-specific options are as follows:

AMPLEOPTS: This is the final variable used to run the LLVM pass. In the program-specific options we add the transition point used for our multi-phase analysis (more details).

LIBS: The output of our AMPLE pass is an instrumented bitcode that needs to be compiled into the final binary before being launched. To compile the bitcode, we also need to link it with the libraries it depends upon. This variable contains a list of the libraries that must be linked with the program.

RUNOPTS: Generating the final policy requires executing the instrumented binary to extract the runtime file path values. To do so, some programs must be run with specific command line options. We specify these options through this variable. Each runtime option must be added separately. For example, we run need to run nginx as follows: nginx -g 'daemon off; master_process off;' and add the options using the following:

RUNOPTS+=("-g")
RUNOPTS+=("daemon off; master_process off;")

SUDO: Some programs need root permissions to launch correctly (e.g., nginx needs root permission if it runs on a port <1024). For these cases, we can enable the SUDO variable so the program is executed with sudo.

Contributing

We do not accept contributions on this project, as it has only been released to allow our paper's data to be reproduced.

License

Distributed under the <License name> License. See LICENSE for more information.

Contact

Hamed Ghavamnia - @s_hamedgh - sghavamnia@bloomberg.net

Project Link: https://github.com/bloomberg/ample

Paper for Reference

Please consider citing our paper if you found our tool useful.

@inproceedings{amplease25,
  year={2025},
  booktitle={Proceedings of the 40th IEEE/ACM International Conference on Automated Software Engineering (ASE)},
  title={AMPLE: Fine-grained File Access Policies for Server Applications},
  author={Ghavamnia, Seyedhamed and Vanegue, Julien}
}

Contributors

shamedgh

1 commits

bloomberg/ample

AMPLE: Fine-grained File Access Policies for Server Applications

C++

0

1 commits

updated Nov 17, 2025

See the code

README

AMPLE: Fine-grained File Access Policies for Server Applications

[!NOTE] We are publishing this tool as open source solely for the purpose of reproducibility of the data in our paper. We will not be providing ongoing maintenance or support for this project.

About The Project

AMPLE builds AppArmor MAC policies for server applications by combining static and dynamic analysis. AppArmor policies can restrict a process from accessing irrelavent (and possibly security-critical) IPC resources. In our current version, AMPLE builds policies to restrict access to files.

To this end, AMPLE first analyzes a program statically, extracting any file paths available in the source code. It then identifies and instruments initialization instructions for those that are only available at runtime. Given that many of the files required by server applications are only specified at runtime (e.g., path to the configuration file specified through a commandline option), AMPLE uses these instrumentations to extract their value at runtime.

To summarize, AMPLE performs the following steps to analyze a program:

  1. Statically analyzes the program through an LLVM pass and instruments the binary
  2. Launches the instrumented binary to extract runtime available file paths
  3. Combines statically extracted file paths with those available at runtime to build the final policy
  4. Validates syntax correctness of the generated policy by enforcing with AppArmor
  5. If the policy passes the syntax correctness, AMPLE attempts to run the program with the policy being enforced to validate the policy's soundness.

We have developed a script that takes care of invoking all the necessary scripts that perform the steps above (discussed further below).

LLVM Pass

We use an LLVM pass to analyze and instrument the program. Our pass is built on top of SVF. It can be used both as a standalone program or with the opt toolkit. However, to utilize AMPLE's runtime analysis, we must instrument the program, which requires running it through the opt toolkit.

Furthermore, our analysis depends on an SVF pointer analysis to generate sound results. Although our implementation allows running AMPLE without enabling SVF, doing so makes the results unsound.

This is due to the following reasons:

  1. We won't have the indirect function call targets resolved, so performing the interprocedural data flow analysis will not be sound.
  2. Our multiphase analysis -- which identifies whether an API call or its value's initialization happen only during the initialization phase -- relies on having the results of pointer analysis for the indirect call targets. Therefore, AMPLE will disable the multiphase analysis if/when pointer analysis is not enabled.
  3. Our nested-type analysis relies on memory object types to extract their values. If the address of any of these types are passed through a function, it could be used through another type, making our nested-based analysis incorrect. Therefore, we must perform pointer analysis for these cases.

NOTE If the provided script (runAll.sh) is used, all the required options will be used when running our analysis, and the issues mentioned above will be addressed.

Python Script

The LLVM pass generates an initial policy that requires bootstrapping to be converted to the final policy. We have created a Python script that takes the LLVM pass output as its input and combines it with runtime file paths extracted by running the instrumented binary to generate a final policy. This is the policy that is used to enforce AppArmor for the program.

Getting Started

Prerequisites

If you choose to build our pass on your own system, the following prerequisites must be met:

  • Build gold linker (binutils)
  • Build LLVM 12
  • Build SVF (use the SVF provided in our repoository, since we have made modifications to it)
  • Install packages: sudo apt install python3 apparmor apparmor-utils

NOTE: We have used SVF v2.2 in our repository, which is compatible with LLVM v12. As of October 2025, the most recent SVF version is 3.1 which is compatible with LLVM v16. If you need to run AMPLE with a newer LLVM version, then you must modify both our AMPLE LLVM pass and SVF to make it compatible with the newer LLVM version.

Build binutils-2.42

To build binutils-2.42 from the source code, we must follow these steps:

wget https://ftp.gnu.org/gnu/binutils/binutils-2.42.tar.xz
tar -xvf binutils-2.42.tar.xz
mkdir binutils.build
../binutils-2.42/configure --enable-gold --enable-plugins --disable-werror
make all-gold

Build LLVM 12

To build LLVM 12 from the source code with the Gold plugin support, we must follow these steps (taken from the SVF repository):

Then run the following commands:

tar xf llvm-12.0.0.src.tar.xz
tar xf clang-12.0.0.src.tar.xz
mv clang-12.0.0.src llvm-12.0.0.src/tools/clang

Create a build directory and then run the following:

mkdir llvm-12.0.0.obj
cd llvm-12.0.0.obj
cmake -DLLVM_BINUTILS_INCDIR=/huge/hamed/tools/binutils-2.42/include/ -DCMAKE_BUILD_TYPE=Release ../llvm-12.0.0.src (or add "-DCMAKE_BUILD_TYPE:STRING=Debug" for debug version)
make -j8

Create a setenv.sh script that sets paths for LLVM and SVF in the required environment variables:

export LLVM_SRC=your_path_to_llvm-12.0.0.src
export LLVM_OBJ=your_path_to_llvm-12.0.0.obj
export LLVM_DIR=your_path_to_llvm-12.0.0.obj
export PATH=$LLVM_DIR/bin:$PATH
export SVF_DIR=[AMPLE_ROOT]/SVF-2.2

Set the environment variables before proceeding.

chmod +x setenv.sh
source setenv.sh

Build SVF

To build SVF you need to create build directory, set up the build scripts using cmake and then build using make.

# Under ample/SVF-2.2:
mkdir Release-build
cd Release-build

cmake .. # If no luck with default compiler, CC=clang CXX=clang++ cmake ..
make -j8

Installation

Build AMPLE Pass

After building LLVM and SVF and adding the paths to the PATH environment variable, we can build the project by creating a build folder and using CMake.

  1. Clone the repository

    git clone https://github.com/bloomberg/ample.git
    
  2. Build the LLVM pass

    cd [AMPLE_ROOT]/src/c++/syscall-arg-analyzer;
    mkdir build
    cd build
    CC=clang CXX=clang++ cmake ../ (add -DCMAKE_BUILD_TYPE:STRING=Debug for debug build)
    
  3. Run the pass

    ./bin/ample
    

Please keep in mind that we need to use the LLVM-provided opt to run the analysis if we want to instrument the binary to extract runtime values. By running AMPLE itself, you can only perform static analysis and identify how many values can be (potentially) extracted at runtime. The runtime values themselves need to be extracted by running the instrumented program.

Fix Default Paths

The scripts which run the analysis rely on certain environment-specific variables to find the paths to the compiled AMPLE pass and other scripts they depend upon. The paths required by this script are the following:

export AMPLE_HOME="[path_to_ample]/ample/" # Specifies path to root of ample repo
export AMPLE_BUILD="[path_to_ample]/ample/src/c++/syscall-arg-analyzer/Debug-build.chain-overhaul" # Specifies path to build of ample
export SVF_DIR="[path_to_ample]/ample/SVF-2.2/"  # Path to SVF src directory
export LLVM_DIR="[path_to_llvm]/llvm-12.0.0.obj/"      # Path to LLVM build directory
export APIARG_DIR="$AMPLE_HOME/data/"   # Path to folder which contains file with api-arg pairs we want to extract values for

These variable are set through a setenv.sh file located in [AMPLE_ROOT]/src/c++/syscall-arg-analyzer. Assuming that the LLVM_DIR and SVF_DIR have been set above you only need to change the AMPLE_HOME and AMPLE_BUILD variables.

AMPLE_HOME: This variable should contain the path to the root AMPLE directory. This is the main repository folder on your local system.

AMPLE_BUILD: This variable should contain the path to the local AMPLE build directory.

Please modify these paths before proceeding to the next steps.

Generate Program's Bitcode

Our analysis takes the LLVM IR as its input. Therefore, to run our analysis on an application or library, you need to first generate its bitcode. We have provided a guide on how to do that.

AMPLE expects the developer to provide the transition point between the initialization and processing phases for its multi-phase analysis. Through its multi-phase analysis, AMPLE can determine whether a file path is initialized during the program startup or in its processing phase. To do so, you must annotate the source code; explanation here.

Also, to add instrumentations for extracting runtime-available file path values, we must link the target program's bitcode with our instrumentation functions. These functions are then invoked when an initialization instruction is identified. We have created a script for adding these instrumentations to each program:

cd [AMPLE_ROOT]/src/c++/syscall-arg-analyzer/instrumentation
clang -emit-llvm -c ample_instrument.c -o ample_instrument.bc
./instrument-all.sh [folder_with_original_bitcodes] ample_instrument.bc $AMPLE_HOME/bc.outputs [appname]

The [appname] in this command is optional and can be used to run the script for only one program.

NOTE: This script only adds the instrumentation functions to the program bitcode. We must run the LLVM pass to identify the instructions that must be instrumented and add function calls to those instrumentation functions.

Usage

We have created a runAll.sh script to run all the steps needed to generate an AppArmor policy using AMPLE. The runAll.sh script which is in the src/c++/syscall-arg-analyzer folder takes care of all the options that need to be passed to AMPLE.

NOTE: After generating an instrumented LLVM bitcode, AMPLE compiles the bitcode into the final binary. For this step, we must have installed any libraries that the program depends upon so that it can be linked with the program. The dependencies for all the programs in our dataset are as follows:

sudo apt install libssl-dev libpcre2-dev libpcre2-8-0 libpam0g-dev libfastjson-dev uuid-dev libevent-dev libxml2-dev libcap-dev libdb-dev zlib1g-dev

NOTE: AMPLE needs to run the target application to extract the runtime-based file paths. Therefore, each application must have a valid configuration file with which it can be run. Valid configuration files have been provided for the applications in our test set. However, in case the runtime setting of one of these applications conflicts with another program running on the target system, it may lead to invalid policy generation. It is critical to go through and check these issues before running AMPLE for an application. For example, based on our provided configuration file for Nginx, it will run on port 8080. Therefore, if another program is running on this port, you need to modify Nginx's port before running AMPLE. The configuration files for all applications can be found in [AMPLE_HOME]/app_run_env.

NOTE: Some programs have other environment-related properties in their configuration file that need to be modified before being executed. The settings have been listed below: - Lighttpd: The lighttpd.conf file found in our app_run_env folder specifies the user and group to run lighttpd with. You must change the user and group defined there to a valid user and group on your system. Furthermore, you need to change the same properties which are also used in the setup_run_env.sh script of lighttpd. This file can be found in [AMPLE_ROOT]/app_run_env/lighttpd.

To run the script we use the following command:

./runAll.sh [appname] instrument validate

Instead of [appname], you can either specify the application name for which you would like to run the analysis or use 'all' to run it for all of the apps in our dataset. The instrument option is optional, it specifies whether an instrumented bitcode should be generated or only the static analysis portion should be run. If the instrument option is passed, you can also pass the validate option to validate the generated policy by running the program.

Examples:

./runAll.sh memcached

This would run the analysis for memcached and not build an instrumented binary. The generated policy would only contains paths derived statically.

./runAll.sh memcached instrument

This would run the analysis for memcached, but also create an instrumented binary. Even though the instrumented binary is generated, you stil need to pass the validate option for the script to run the program and extract runtime-available file paths as well.

./runAll.sh memcached instrument validate

This command would run all the steps and generate an AppArmor policy which contains both statically derived and dynamically derived policies.

The applications currently available (when using runAll.sh the names should have the same syntax as specified below):

  • memcached
  • redis
  • nginx
  • exim
  • lighttpd
  • snmpd
  • proftpd
  • smtpd
  • monkey
  • named

By default, the following paths will be used to store the output of the analysis:

  • $AMPLE_HOME/logs: full log of running the analysis will be stored separately for each application in this folder (named by the app)
  • $AMPLE_HOME/policies: the statically derived policy, and the final policy will be stored in two separate policy files in this folder
  • $AMPLE_HOME/bc.outputs: if the instrument option is specified the instrumented bitcode will be stored in this path
  • $AMPLE_HOME/bin.outputs: if the instrument option is specified the instrumented binary will be stored in this path

Further Details

The Python script and its respective files are located in [AMPLE_ROOT]/src/python. To use the Python script, we first need to provide its settings through the config.json file available in this folder. These settings include general options that are the same for all programs across the same OS, and application-specific options that are specified for each program separately.

The general-purpose options include: enforce-cmd and disable-cmd which specify how to enable and disable an AppArmor policy (this might change in different OS versions). Another option is policy-folder which is the default path to store the generated policies.

The next option is libc-required-libraries. The standard C library (glibc) can dynamically load other libraries. Since we do not analyze glibc statically --- because it cannot be compiled with LLVM --- we expect these libraries to be provided manually. This should not change regularly. However, if it does, these library names or paths should be specified here. If the library name pattern is provided, our script will attempt to find a library file matching this name and add its full path. If the path is provided, AMPLE will add that path as is to the final policy.

We also have application-specific options which are configured in the apps section of the JSON file. We have already provided the settings needed for the 10 applications in our dataset. You must configure these settings when adding a new program to the dataset. You can override some of these options through the commandline when invoking the Python script. Each application has six main properties which should be configured, however, if you use the runAll.sh script to run AMPLE, it will override all the file paths specified in this JSON file when invoking this Python script, so the correct paths are passed to the script. Therefore, there is no need to modify these paths in the JSON file.

"memcached": {
    "llvm-policy-path": "[llvm-policy-path]",
    "format" : "apparmor",
    "binary-path": "/home/ssm-user/analysis/apps/memcached-1.6.9/memcached",
    "runtime-paths-auto": "/tmp/ample/memcached.instrumented",
    "runtime-paths-manual": {
    },
    "caps-manual": [
        "setgid",
        "setuid"
    ]
},

llvm-policy-path: path to the policy built by the LLVM pass --- no need to change if the Python script is called through runAll.sh

format: for now we only support AppArmor policies, but it can be extended to support SELinux

binary-path: path to the binary of the target application or library. This is required to extract the libraries linked to it --- no need to change if the Python script is called through runAll.sh

runtime-paths-auto: the path to the files where we store runtime-extracted file path values --- no need to change if the Python script is called through runAll.sh

runtime-paths-manual: a dictionary with paths specified at runtime along with their permissions.

caps-manual: AppArmor policies can restrict the capabilities used by a program as well. Our current AMPLE design does not support automatically extracting program's required capabilities, so we must add the program's needed capabilities here.

This script will be run when using the runAll.sh script.

The runAll.sh Script

The runAll.sh script contains all the options and commands to run AMPLE for a program. In this section we have provided details on this script and its parameters.

Since runAll.sh executes both the LLVM pass and the Python script, we have defined the required command line options for each of these analyses in this script.

LLVM Pass Options

We will first describe the LLVM pass command line options that determine which optional features of the pass should be executed. (To see a full list of all the available runtime options for AMPLE's LLVM pass we can use: [AMPLE_BUILD_DIR]/ample --help | grep AMPLE) These options are defined in the following variable names: AMPLEOPTS: The final options used to run the LLVM pass.

GENERALOPTS: These are the LLVM pass options that we are currently using when analyzing any program. At some point, we could specify these values in the LLVM pass source as the default values and refrain from repeating them here.

TYPEOPT: Our pass uses type-based argument filtering for reducing the spurious indirect function call edges. This was used in the Temporal System Call Specialization paper. We do not add this option in the general options (GENERALOPTS), because it can lead to soundness issues for programs developed with C++.

INSTRUMENTOPT: This variable adds the -enable-instrument option which can only be used if the pass is run through opt. It enables the instrumentation part of the program.

PTSLOOP: This variable adds the -pts-based-depth option which is an experimental feature that is not fully tested yet.

These are the options that are defined the same across all programs. We also have program-specific options that are defined in the if clauses in the script. The program-specific options are as follows:

AMPLEOPTS: This is the final variable used to run the LLVM pass. In the program-specific options we add the transition point used for our multi-phase analysis (more details).

LIBS: The output of our AMPLE pass is an instrumented bitcode that needs to be compiled into the final binary before being launched. To compile the bitcode, we also need to link it with the libraries it depends upon. This variable contains a list of the libraries that must be linked with the program.

RUNOPTS: Generating the final policy requires executing the instrumented binary to extract the runtime file path values. To do so, some programs must be run with specific command line options. We specify these options through this variable. Each runtime option must be added separately. For example, we run need to run nginx as follows: nginx -g 'daemon off; master_process off;' and add the options using the following:

RUNOPTS+=("-g")
RUNOPTS+=("daemon off; master_process off;")

SUDO: Some programs need root permissions to launch correctly (e.g., nginx needs root permission if it runs on a port <1024). For these cases, we can enable the SUDO variable so the program is executed with sudo.

Contributing

We do not accept contributions on this project, as it has only been released to allow our paper's data to be reproduced.

License

Distributed under the <License name> License. See LICENSE for more information.

Contact

Hamed Ghavamnia - @s_hamedgh - sghavamnia@bloomberg.net

Project Link: https://github.com/bloomberg/ample

Paper for Reference

Please consider citing our paper if you found our tool useful.

@inproceedings{amplease25,
  year={2025},
  booktitle={Proceedings of the 40th IEEE/ACM International Conference on Automated Software Engineering (ASE)},
  title={AMPLE: Fine-grained File Access Policies for Server Applications},
  author={Ghavamnia, Seyedhamed and Vanegue, Julien}
}

Contributors

shamedgh

1 commits

Languages

C++

56.2%

Python

17.3%

HTML

13.7%

Makefile

6.5%

Shell

4.9%