暂无图片
暂无图片
2
暂无图片
暂无图片
暂无图片

PostgreSQL hook机制

原创 Maleah 2022-08-16
4664

前言

hook机制来源于Windows平台。

hook俗称钩子,是一个 Windows 消息的拦截机制。每当特定的消息发出,在没有到达目的窗口前,钩子程序就先截获该消息。这时钩子函数就可以加工处理(改变该消息),也可以强制结束消息的传递(屏蔽消息)

img

钩子的种类很多,每种钩子可以截获相应的消息,如键盘钩子可以截获键盘消息,外壳钩子可以截取、启动和关闭应用程序的消息等。钩子可以分为线程钩子和系统钩子,线程钩子可以监视指定线程的事件消息,系统钩子监视系统中的所有线程的事件消息。因为系统钩子会影响系统中所有的应用程序,所以钩子函数必须放在独立的动态链接库 (DLL) 中。
所以说,hook(钩子)就是一个 Windows 消息的拦截机制,可以拦截单个进程的消息 (线程钩子),也可以拦截所有进程的消息 (系统钩子),也可以对拦截的消息进行自定义的处理。

常用的hook

该机制提供了一种修改PostgreSQL内核功能却不必修改内核代码的手段,且可以轻松的加载和还原。

image.png

image.png

hook机制,提供了一种修改PostgreSQL内核功能却不必修改内核代码的手段,且可以轻松的加载和还原。

在PG中也可以把hook看作是一种内置的过滤器,但他是个空壳,过滤器里什么都没有,可以实现这个hook,也就是往过滤器里加东西。

PostgreSQL使用HOOK时,根据HOOK的名字找到对应的定义,通常是HOOK被定义时,将跳转到对应的代码执行用户在插件中植入的代码片段

内部机制

  • PG 中 hook 如何工作

Hooks 存在一个全局函数指针,并初始为 NULL,表示hook未被使用。当PostgreSQL执行到hook处的时候,检查这个全局函数指针,如果hook被设置为某个函数的地址时,转而执行新增的函数;否则执行标准函数

拿ProcessUtility来说,有下面这样一段代码:

逻辑 - 如果有钩子函数,就执行钩子函数;否则执行标准函数

image.png

  • 如何设置函数指针

hook 函数定义在共享库中。在加载共享库时,PG 会调用共享库中的_PG_init() 函数。通过_PG_init 函数设置函数指针(一般会把原来的指针保存)。

可以通过LOAD命令来实现动态加载共享库文件,也可以通过修改session_preload_libraries 或 shared_preload_libraries 参数来加载

  • 如何清除函数指针

在卸载时,PG 调用共享库中的_PG_fini() 函数

_PG_fini 函数需要重新设置函数指针(通常将原来的指针恢复)。

这里要提一句,PG15版本自封装的扩展已经不包含 _PG_fini() 函数。且在官方文档里也指出:

a dynamically loaded file can contain an initialization function. If the file includes a function named _PG_init, that function will be called immediately after loading the file. The function receives no parameters and should return void. There is presently no way to unload a dynamically loaded file.

目前没有办法卸载动态加载的文件。

所以在编写函数文件时_PG_fini() 函数可以忽略

举例说明

那么问题来了,如何写一个被PostgreSQL所接受的共享库呢?

需求

删除数据库foodb时,只能使用foo用户删除,其他用户(包括超级用户)均不能删除此数据库。

思路

删除数据库的命令是drop database,属于Utility命令,而PG提供了ProcessUtility_hook钩子可供使用

1)_PG_init() 函数
void _PG_init(void)
{
        prev_utility_hook = ProcessUtility_hook;
        ProcessUtility_hook = dbrestrict_utility;
}
2)_PG_fini()函数(可选)

移除hook并且把它重置为之前的指针值

void _PG_fini(void)
{
        ProcessUtility_hook = prev_utility_hook;
}
3)自定义的hook函数 - dbrestrict_utility()

保存当前的hook值(保证你移除修改后能够还原),并将你的hook挂在到PostgreSQL的hook函数指针上。

static void dbrestrict_utility(PlannedStmt *pstmt, const char *queryString,bool readOnlyTree, ProcessUtilityContext context, ParamListInfo params, QueryEnvironment *queryEnv, DestReceiver *dest, QueryCompletion *qc) { /* 检查是否为空 */ switch (nodeTag(pstmt->utilityStmt)) { /* 该命令是 DROP DATABASE */ case T_DropdbStmt: { DropdbStmt *stmt = (DropdbStmt *) pstmt->utilityStmt; /* 获取 username */ char *username = GetUserNameFromId(GetUserId(),false); /* * Check that only the authorized superuser foo can * drop the database undroppable_foodb. */ /* 只有当 superuser 权限的 foo用户可以 drop foodb 数据库*/ if (strcmp(stmt->dbname, undroppabledb) == 0 && strcmp(username, supersuperuser) != 0) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("Only super-superuser \"%s\" can drop database \"%s\"", supersuperuser, undroppabledb))); break; } default: break; } /* * Fallback to normal process, be it the previous hook loaded * or the in-core code path if the previous hook does not exist. */ if (prev_utility_hook) (*prev_utility_hook) (pstmt, queryString, readOnlyTree, context, params, queryEnv, dest, qc); else standard_ProcessUtility(pstmt, queryString, readOnlyTree, context, params, queryEnv, dest, qc); }

上述三个函数加在一起,构成了最基础的PostgreSQL的hook

4)Makefile文件
MODULE_big = dbrestrict  
OBJS = $(WIN32RES) dbrestrict.o

# postgres build stuff
ifdef USE_PGXS
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)
else
subdir = contrib/dbrestrict
top_builddir = ../..
include $(top_builddir)/src/Makefile.global
include $(top_srcdir)/contrib/contrib-global.mk
endif

MODULE_big:根据OBJS 里面的.o 文件生成的动态库

make & make install

生成 .so 文件并拷贝到lib文件夹下

测试验证

-- 创建测试数据库
postgres=# create database foodb ;
CREATE DATABASE
--- 加载前:成功 drop
postgres=# drop database foodb ;
DROP DATABASE
--- 动态加载共享库文件:dbrestrict.so
postgres=# load 'dbrestrict.so';
LOAD
-- 创建测试数据库
postgres=# create database foodb ;
CREATE DATABASE
--- drop 失败
postgres=# drop database foodb ;
ERROR:  Only super-superuser "foo" can drop database "foodb"
-- 创建带有superuser属性的foo用户
create user foo superuser ;
--- drop 成功
postgres=# \c - foo
You are now connected to database "postgres" as user "foo".
postgres=# drop database foodb ;
DROP DATABASE

--# 需求测试完成!

参考

https://paquier.xyz/postgresql-2/hooks-in-postgres-super-superuser-restrictions/

https://wiki.postgresql.org/images/e/e3/Hooks_in_postgresql.pdf

https://www.cnblogs.com/flying-tiger/p/7801258.html

https://developer.aliyun.com/article/603965?scm=20140722.184.2.173

(13条消息) postgresql Hook机制加实例详解_liguangxian2018的博客-CSDN博客_hook postgresql

附录

两个文件:

  • dbrestrict.c

    /* * dbrestrict.c * Restrict drop of a given database to a given super-superuser only. * Under license "do whatever you want with that" */ #include "postgres.h" #include "miscadmin.h" #include "tcop/utility.h" PG_MODULE_MAGIC; // 声明函数 void _PG_init(void); void _PG_fini(void); static char *undroppabledb = "foodb"; static char *supersuperuser = "foo"; //全局变量初始化为NULL static ProcessUtility_hook_type prev_utility_hook = NULL; //声明函数 static void dbrestrict_utility(PlannedStmt *pstmt, const char *queryString,bool readOnlyTree, ProcessUtilityContext context, ParamListInfo params, QueryEnvironment *queryEnv, DestReceiver *dest, QueryCompletion *qc); /* * Personal process for DB drop restriction */ static void dbrestrict_utility(PlannedStmt *pstmt, const char *queryString,bool readOnlyTree, ProcessUtilityContext context, ParamListInfo params, QueryEnvironment *queryEnv, DestReceiver *dest, QueryCompletion *qc) { /* Do our custom process on drop database */ switch (nodeTag(pstmt->utilityStmt)) { case T_DropdbStmt: { DropdbStmt *stmt = (DropdbStmt *) pstmt->utilityStmt; char *username = GetUserNameFromId(GetUserId(),false); /* * Check that only the authorized superuser foo can * drop the database undroppable_foodb. */ if (strcmp(stmt->dbname, undroppabledb) == 0 && strcmp(username, supersuperuser) != 0) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("Only super-superuser \"%s\" can drop database \"%s\"", supersuperuser, undroppabledb))); break; } default: break; } /* * Fallback to normal process, be it the previous hook loaded * or the in-core code path if the previous hook does not exist. */ if (prev_utility_hook) (*prev_utility_hook) (pstmt, queryString, readOnlyTree, context, params, queryEnv, dest, qc); else standard_ProcessUtility(pstmt, queryString, readOnlyTree, context, params, queryEnv, dest, qc); } /* * _PG_init * Install the hook. */ void _PG_init(void) { prev_utility_hook = ProcessUtility_hook; ProcessUtility_hook = dbrestrict_utility; } /* * _PG_fini * Uninstall the hook. * 如果卸载则调用该函数,实际上是将ProcessUtility_hook赋回原值 */ void _PG_fini(void) { ProcessUtility_hook = prev_utility_hook; }
  • Makefile

这里只是对 HOOK 机制做个初步的了解

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论