Pollen 编程语言
安全漏洞的主要来源是指针。
- 没有指针,就没有空指针解引用。
- 没有指针,就没有指针运算。
- 没有指针运算,就没有数组越界。
传统上,C 源代码分为 .c 和 .h。C 源代码不包含指针。头文件包含使用指针的宏。
头文件可用于定义 .c 文件使用的类型定义。类似于变量声明,新的标识符位于 typedef 的右侧。以下声明指向 my_struct 记录的指针为 my_struct_t。传统上,_t 表示“类型”,用于将类型标识符与变量和参数标识符区分开来。
/* typedef <existing_name> <alias_name>; */
struct my_struct {
int my_field;
};
typedef struct my_struct *my_struct_t;
Or simply...
typedef struct my_struct {
int my_field;
} *my_struct_t;
在源文件上禁止使用符号 "&" 和 "*"。
使用内存池在堆上分配新对象。Apache APR 库提供了内存池的良好实现。可以将 API 作为实现内存池的参考。
为 C++ 编程语言开发的面向对象的概念也适用于 C 编程语言。可以通过使用用于 getter 和 setter 的宏来实现。
getter 和 setter 在对表达式使用之前检查空指针解引用。GNOME 项目中的 GLib 运行时库是用 C 编写的,并使用此策略来避免内存错误。维护该库的安全至关重要,因为 GLib 运行时库用于所有 GNOME 项目。以下示例演示了在 C 中使用宏进行面向对象的示例
#define NEW_CODEGEN_PLUGIN(ctx) \
(tmpl_codegen_t) pollen_malloc(ctx, sizeof(struct pollen_codegen_plugin));
#define CODEGEN_SET_CONTEXT(codegen, x) \
((codegen != NULL)? (codegen->ctx = x) : (abort(), NULL))
#define CODEGEN_GET_CONTEXT(codegen) \
((codegen != NULL)? (codegen->ctx) : (abort(), NULL))
#define CODEGEN_SET_LIBRARY(codegen, lib) \
((codegen != NULL)? (codegen->library = lib) : (abort(), NULL))
/* Macro to get the library handle from the plugin context */
#define CODEGEN_GET_LIBRARY(codegen) \
((codegen != NULL)? (codegen->library) : (abort(), NULL))
在上面的代码中,abort() 函数调用后的逗号允许将 NULL 作为子表达式 (abort(), NULL) 的结果返回给父表达式,即 ...? ... : .... abort 函数返回 void。
使用未知类型的函数也应该使用宏来执行强制转换。
/* Macro to call a function with no arguments */
#define POLLEN_CALL_SYMBOL_0(symbol, rc) \
do { \
int (*fn_ptr)() = (int (*)()) symbol; \
rc = fn_ptr(); \
} while(0);
#define NEW_CODEGEN_PLUGIN(ctx) \
(tmpl_codegen_t) templatizer_malloc(ctx, sizeof(struct pollen_codegen_plugin));
除了安全编程语言之外,还可以使用虚拟寻址。这允许开发人员包含用旧版编程语言编写的旧版代码。
只有 root 用户可以运行将作为不同用户运行的程序(setuid 除外)。守护进程可以分叉其进程,以便在非特权用户下运行进程,以隔离彼此的每个 API 请求。
因此,唯一的主要问题是解析请求输入(例如 JSON 或 XML)的程序部分,这些程序包含请求的身份验证数据以及套接字连接本身。处理此类数据的服务器部分应由用安全语言编写的经过仔细验证的框架处理,而包含用旧版编程语言编写的代码的部分应位于分叉或执行的 worker 进程中。
Ada 是一种专注于内存安全以及程序整体正确性的编程语言。存在 SPARK 子集(不是过时的 SPARC 架构),它从数学上证明给定程序没有缺陷。
美国政府制造的阿里安火箭是用 Ada/SPARK 编写的,由于软件缺陷而爆炸。当讨论 Ada 作为一种非常安全的语言的有效性时,这个问题总是出现,但实际上软件缺陷是由于使用了 pragma 而存在的。包含导致火箭爆炸的部分的实际源代码片段可供公众通过美国宇航局获取,并且仍然可以在这些日子里在线找到,远在爆炸成为新闻之后。也许它仍然是一个禁忌,因为它仅仅是 Ada 确实有缺陷的“证据”。
正如我提到的,它是由于错误使用 pragma 造成的,该 pragma 对火箭的硬件系统做出了错误的假设,因为它是从之前具有完全不同硬件的火箭移植过来的。
此外,Ada 中的 pragma 是特定于编译器的,而不是语言的一部分。语言没有对 pragma 提供相同的安全保证,因为正如我之前所说,大多数 pragma 并未在标准中定义。Ada 标准规范定义了关键字“pragma”,但它们表示是特定于编译器的自定义代码。
根据 Ada 标准,不允许在字符串中使用 0xFFFE 和 0xFFFF 字节序列。Ada 运行时会修复无效的字符串。此限制会导致 UTF-8 出现错误。
不允许在程序文本中的任何位置使用其平面中相对代码位置为 16#FFFE# 或 16#FFFF# 的字符。
这些字节序列在 UTF-8 中用于表示表情符号。
用 Ada 编写的程序通常需要一个带有自己的类型和函数的附加运行时。该运行时可能有一部分是用 Ada(绑定)编写的,而另一部分是用 C 等其他语言编写的。
这正是 Pollen 发挥作用的地方。Pollen 可用作 Ada 的运行时,用于访问现代操作系统功能、外部库或 Ada 运行时未涵盖的其他功能。
以下是 Ada 运行时未涵盖的一些很好的功能示例
- UTF-8;
- 内存流;
- 数据库支持;
- 现代压缩算法;
- 现代密码算法;
- 网络(Berkeley 套接字)。
如果用作运行时,Pollen 将为 Ada 提供所有这些功能。
创建作为操作系统或微控制器上的主要代码使用的独立 Ada 代码比在 C 中创建独立代码要复杂得多。
创建独立 Ada 代码更加困难,因为 Ada 需要运行时才能编译和运行。但要理解的重要一点是,Pollen 应该遵循以下示例:将运行时代码作为独立代码在裸机上运行的语言所需的所有运行时代码都包含在标准中,并且公开可用的文档,以便内核开发人员可以有效地使用该语言。
Ada 运行时是用 Ada 本身编写的,并使用 pragma Import 指令调用 C 代码来运行依赖于语言未提供的安全功能的代码,例如
- 指针和指针运算;
- 异常处理;
- 任务;
- 延迟;
- 数组边界检查;
- 等等。
这些都是跨编译器、操作系统和硬件可移植的。运行 Ada 代码的硬件不一定需要提供所有这些功能,因此并非每个功能都必须在特定硬件或操作系统上存在。所有这些功能都在 Ada 语言规范标准中定义。
Ada 代码通常依赖于编译器生成的代码,例如用于检查指针解引用时的空值检查的代码。但是,这些运算符(例如此例中的解引用运算符)可以手动覆盖。
这些自动生成的代码通常不会对低级开发人员构成危险,因为它们调用了 Ada.Exceptions 包的异常处理程序。如果程序员想支持指针解引用和比较运算符的空值检查,只需要在正在创建的操作系统的内核的 Ada 运行时创建该包上的适当处理程序。
溢出检查由 Ada 运行时通过使用运算符重载完成,就像面向对象的代码一样。
function "+" (Left, Right : Integer) return Integer;
pragma Import (C, "+", "my_overflow_checking_function");
Ada 语言没有提供类型。它们都是由运行时定义的,溢出检查通常被指定为编译器的内在函数,带有
pragma Import (Intrinsic, "+");
全局分配器由 Root_Storage_Pool 类型及其方法的定义创建,也像面向对象的代码一样。
5
with Ada.Finalization;
with System.Storage_Elements;
package System.Storage_Pools is
pragma Preelaborate(System.Storage_Pools);
6/2
{AI95-00161-01} type Root_Storage_Pool is
abstract new Ada.Finalization.Limited_Controlled with private;
pragma Preelaborable_Initialization(Root_Storage_Pool);
7
procedure Allocate(
Pool : in out Root_Storage_Pool;
Storage_Address : out Address;
Size_In_Storage_Elements : in Storage_Elements.Storage_Count;
Alignment : in Storage_Elements.Storage_Count) is abstract;
8
procedure Deallocate(
Pool : in out Root_Storage_Pool;
Storage_Address : in Address;
Size_In_Storage_Elements : in Storage_Elements.Storage_Count;
Alignment : in Storage_Elements.Storage_Count) is abstract;
9
function Storage_Size(Pool : Root_Storage_Pool)
return Storage_Elements.Storage_Count is abstract;
10
private
... -- not specified by the language
end System.Storage_Pools;
创建您的 Ada 源文件,并使用 GNAT 编译器 gcc 将它们编译为目标文件。例如,假设您有两个名为 mypackage.ads 和 mypackage.adb 的 Ada 源文件,并且您想将它们编译为目标文件
gcc -c mypackage.adb
创建一个 C 源文件,它将调用 Ada 库函数。在本例中,假设您想调用一个名为 my_ada_function 的函数,该函数在 mypackage.ads 中定义。C 源文件可能如下所示
#include <stdio.h>
#include <stdlib.h>;
#include "mypackage.h"
int main(int argc, char** argv) {
int result = my_ada_function();
printf("Result: %d\n", result);
return 0;
}
创建一个 C 头文件 (mypackage.h),声明您要调用的 Ada 函数。在本例中,头文件可能如下所示
#ifndef MYPACKAGE_H
#define MYPACKAGE_H
#ifdef __cplusplus
extern "C" {
#endif
int my_ada_function();
#ifdef __cplusplus
}
#endif
#endif /* MYPACKAGE_H */
使用 GNAT 编译器为您的 Ada 包生成 C 绑定
gnatbind -Llibada.a -static -I. -C mypackage
使用 C 编译器编译 C 源文件,并将其与 Ada 库文件和 C 绑定文件链接
#!/bin/bash -e
gcc -c mypackage.adb.c
gcc -c main.c
gcc -o myprogram main.o mypackage.o -L. -lada
Ada 标准库使用宽字符处理 Unicode。Interfaces.C.Strings 包不支持 UTF-8 或宽字符编码。明智的做法是在 C 端而不是 Ada 端包含字符转码函数。这些函数应该将 Unicode 字符串转换为 Ada Wide_String 及类似字符串。
with Interfaces.C;
use Interfaces.C;
with Interfaces.C.Strings;
use Interfaces.C.Strings;
package body Pollen.Strings is
-- ...
function Value (Input : chars_ptr)
return Wide_String
is
function To_Ada_Encoding (Input : chars_ptr; Output : Wide_String) return int;
pragma Import (C, To_Ada_Encoding, "tmpl_to_ada_encoding");
Output : Wide_String (1 .. Strlen (Input));
Return_Code : int;
begin
Return_code := To_Ada_Encoding (Input, Output);
if Return_Code /= 0 then
throw Program_Error with "Unable to convert C string to Pollen string.";
endif;
return Output;
end Value;
-- ...
end Pollen.Strings;
- GNU libjit (libgccjit-10-dev)
- LLVM 字节码
Kaleidoscope 是一个使用 LLVM 代码生成的 JIT 语言示例。
llvm-config 类似于 pkg-config,但它专门用于 LLVM 代码生成。llvm-config 必须在 $PATH 中才能工作。预计只有一个与该 llvm-config 相关的 llvm 工具链。该工具链负责代码生成,并且可能具有不同的体系结构作为目标。
使用 llvm-dev 生成位码的步骤
LLVM_CFLAGS=$(shell llvm-config --cflags)
LLVM_LDFLAGS=$(shell llvm-config --ldflags --libs)
- 创建一个新的 Pollen 插件
- 添加 CFLAGS 和 LDFLAGS llvm-config
- 创建模块;
- 添加代码;
- 添加基本块;
- 将基本块追加到内存中的程序;
- 将位码写入 HDD 或 SSD 上的目标文件或 BC 文件。
欢迎对 Pollen 项目的贡献。
您可以创建一个 bash 脚本,自动插入您的 Git 用户名和密码。使用简单的 HTTP 身份验证将代码推送到 Git 存储库。
#!/bin/bash -e
git push https://mygithubuser:[email protected]/ativarsoft/pollen-lang.git
Pollen 需要运行本地代码,因此典型的 PHP 托管服务器将无法正常工作。截至 2023 年 3 月,很难找到免费的 CGI、Docker 和 VPS 托管。
Google 在其 Cloud Run 上提供适合个人使用的免费托管。
libpollen 运行时有一个名为 hello world 的函数。该函数应该从 tmpl 脚本文件中调用。libpollen 运行时是可选的,使用 Pollen 编写的程序不需要它就能运行。
编译器和解释器以不同的方式获取 "hello_world" 函数的符号。
解释器使用 dlopen 在脚本选择的运行时库中获取符号。选择脚本是 "pollen" 元素上的 "lib" 属性。
编译器通过使用链接器获取符号,因为从脚本创建的目标文件与 libpollen 链接在一起。
- Mateus de Lima Oliveira