Overview

In the MUFL programming language, certain primitives are classified as “dangerous”.

For the complete list of dangerous primitives, refer to Dangerous Primitives.

One example is the _read primitive, which initiates the deserialization of input data. This primitive is considered dangerous because the input data could potentially be malicious, especially if it’s coming from an untrusted source.

MUFL prevents developers from inadvertently using dangerous primitives. ADAPT’s goal isn’t to prohibit the use of dangerous primitives, but rather to make sure developers are aware when using potentially risky functions that could introduce security vulnerabilities in their applications.

In MUFL, dangerous primitives are hidden by default and need to be intentionally exposed to be used. The grab statement creates an alias to expose the primitive, making it available for use. Using dangerous primitives without a grab statement in applications and libraries is prohibited. This requirement forces developers to be aware of dangerous primitives and use discretion to intentionally allow their use.

MUFL doesn’t require using the grab statement in test scripts.

The syntax for the grab statement is:

<primitive_alias> = grab( <primitive_name> ).

The grab statement follows these rules:

  1. The same primitive can’t be grabbed more than once.

  2. Dangerous primitives cannot be used without first being grabbed.

  3. Dangerous primitives can only be grabbed within the main application file.

Using Dangerous Primitives in Applications

Consider this _read primitive example:

application grab_statement_example
{
    _print "Hello, World!\n". // the '_print' primitive is not dangerous and can be used directly
    serialized_data = _write 5. // the '_write' primitive is not dangerous as well
    _read serialized_data. // ERROR! Use of dangerous primitive!
}

which, when compiled, results in:

---COMPILER OUTPUT----
<...> Symbol _read was not found

Compilation Error
ERROR running compiler

Because _read is a dangerous primitive, the _read serialized_data. line results in a compile-time error. These next three examples show ways to resolve the compile-time error:

serialized_data = _write 5.

// Use grab directly
grab( _read ) serialized_data. // Successful read!

Using grab( _read ) serialized_data. to read directly is valid, but allows for only a single use of the _readprimitive, making it unavailable for use elsewhere in your application, which is obviously impractical.

serialized_data = _write 5.

// Create a variable as an alias for the grabbed function
read_alias = grab( _read ). // Stash the function
read_alias serialized_data. // Successful read!

Storing the alias in a variable allows for reading elsewhere. Any valid variable name works.

serialized_data = _write 5.

// Use _read as the variable name
_read = grab( _read ). // Stash the function
_read serialized_data. // Successful read!

Starting the variable with an underscore matches the primitive functions naming convention. You can even use _read without causing a name resolution conflict.

Using Dangerous Primitives in Libraries

Use of dangerous primitives in dependencies is also a security concern and much harder to spot in your code. To use dangerous primitives in a library, you need to use the primitive alias in your library functions. One way to do so is to pass the alias to a library function that is doing the reading:

library dangerous_library
{
    // Use the passed-in _read primitive to read the passed-in data 
    fn deserialize_data (_read, data_to_read: str)
    {
        return _read data_to_read.
    }
}

application main loads library dangerous_library
{
    // Create variable as alias for the grabbed function
    read_alias = grab( _read ). // Stash the function

    // Define data to read
    data_to_read = _write 10.

    // Store primitive alias in the library
    dangerous_library::deserialize_data (read_alias, data_to_read). // We are aware that `dangerous_library uses _read and we allow it 
}

Another way to use the primitive alias in your library functions is to store the alias in the library first:

library dangerous_library
{
    hidden
    {
        // Set the _read variable initially to undefined - NIL
        _read is (bin -> any)+ = NIL.
    }

    // init function receives the alias from main and saves it for later
    fn init (read_alias: (bin -> any))
    {
        // save the primitive alias to the variable
        _read -> read_alias.
    }
    
    // deserialize_data is a function that utilizes the _read primitive 
    fn deserialize_data (data_to_read: bin)
    {
        abort "_read primitive is not grabbed" when _read == NIL.
    
        return _read data_to_read.
    }
}

application main loads library dangerous_library
{
    // Create variable as alias for the grabbed function
    read_alias = grab( _read ). // Stash the function

    // Store primitive alias in the library
    dangerous_library::init (read_alias). // e are aware that `dangerous_library uses _read and we allow it 
}

The main application consciously allows dangerous_library to use the _read primitive by initializing the library.

In the library, the _read variable is initially undefined (set to NIL). The init function takes the _read primitive that has been grabbed by the main application and stores it in the _read variable. The deserialize_data function executes the read. Calling deserialize_data without proper initialization results in an error.

Alternatively, the main application can initialize the library in the previous example with a single statement:

application main loads library dangerous_library
{
    dangerous_library::init (grab( _read )). // we are aware that `dangerous_library` uses `_read` and we allow it 
}

However, without saving the primitive alias before passing it to the library, the primitive becomes unavailable for use elsewhere in the application or libraries other than dangerous_library.