在 PAM 模块(.so 文件)的 C 中嵌入 Python 脚本时出现 ImportError 和 PyExc_SystemError

ImportError and PyExc_SystemError while embedding Python Script within C for PAM modules (.so files)

我正在尝试用 C 编写一个演示 PAM 模块,它使用 C 概念中的嵌入 Python 到 运行 用 python (2.7) 编写的脚本,在 pam_sm_authenticate()函数,写在C文件中(pam_auth.c).

这是 python 脚本:test.py

import math
import numpy
def test_func():
   a = "test"
   return a 

test.py 的路径是 /usr/lib/Python2.7/ 以便我可以轻松导入它。

这是 C 文件:

#define PAM_SM_AUTH
#define PAM_SM_ACCOUNT
#define PAM_SM_SESSION

#include <security/pam_modules.h>
#include <security/_pam_macros.h>
#include <security/pam_appl.h>
#include<python2.7/Python.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

#define NOBODY "nobody"


/*PAM Stuffs*/

PAM_EXTERN int pam_sm_authenticate(
  pam_handle_t* pamh, int flags, int argc, const char** argv)
{
    const char *user;
    int retval;
    user = NULL;
    retval = pam_get_user(pamh, &user, NULL);
    if(retval != PAM_SUCCESS)
    {
        fprintf(stderr, "%s", pam_strerror(pamh, retval));
//      return (retval);
    }
    fprintf(stdout, "retval= %d user=%s\n", retval,user);
    if (user == NULL || *user =='[=13=]')
        pam_set_item(pamh, PAM_USER, (const char*)NOBODY);

    /* Python Wrapper */    

    // Set PYTHONPATH TO working directory
    //int res = setenv("PYTHONPATH",".",1);
    //fprintf(stdout, "%d", res);

    PyObject *pName, *pModule, *pDict, *pFunc, *pValue, *pResult;

    // Initialize the Python Interpreter
    Py_Initialize();

    // Build the name object
    pName = PyString_FromString((char*)"test");

    // Load the module object
    pModule = PyImport_Import(pName);

    // pDict is a borrowed reference 

    PyErr_Print();
    pDict = PyModule_GetDict(pModule);

    // pFunc is also a borrowed reference 
    pFunc = PyDict_GetItemString(pDict, (char*)"test_func");

    if (PyCallable_Check(pFunc))
    {
        pValue=NULL;
        PyErr_Print();
        pResult=PyObject_CallObject(pFunc,pValue);
        PyErr_Print();
    }else 
    {
           PyErr_Print();
    }
    printf("Result is %s\n",PyString_AsString(pResult));

    // Clean up
    Py_DECREF(pModule);
    Py_DECREF(pName);/* */

    // Finish the Python Interpreter
    Py_Finalize();      

    return PAM_SUCCESS;
}

PAM_EXTERN int pam_sm_setcred(
  pam_handle_t* pamh, int flags, int argc, const char** argv)
{
    return PAM_SUCCESS;
}

PAM_EXTERN int pam_sm_acct_mgmt(
  pam_handle_t* pamh, int flags, int argc, const char** argv)
{
    return PAM_SUCCESS;
}

PAM_EXTERN int pam_sm_open_session(
  pam_handle_t* pamh, int flags, int argc, const char** argv)
{
    return PAM_SUCCESS;
}

PAM_EXTERN int pam_sm_close_session(
  pam_handle_t* pamh, int flags, int argc, const char** argv)
{
    return PAM_SUCCESS;
}

PAM_EXTERN int pam_sm_chauthtok(
  pam_handle_t* pamh, int flags, int argc, const char** argv)
{
    return PAM_SUCCESS;
}

C 文件只是 pam_permit.c 的修改版。 C文件使用gcc编译(gcc -shared -o pam_auth.so -fPIC pam_auth.c -I/usr/include/python2.7 -lpython2.7)得到.so文件(pam_auth.so) 并放在文件夹 /lib/security/

我在/etc/pam.d中更改了'sudo'文件的PAM配置如下:

#%PAM-1.0

auth       required   pam_env.so readenv=1 user_readenv=0
auth       required   pam_env.so readenv=1 envfile=/etc/default/locale user_readenv=0
#@include common-auth #this line is commented to make it use my pam module
auth       required   pam_auth.so
@include common-account
@include common-session-noninteractive

行"auth required pam_auth.so"强制系统在我每次使用命令"sudo"时使用我的模块进行身份验证。 (对于前 sudo nautilus)

现在的问题是: C 文件“pModule = PyImport_Import(pName);”中的这一行给出了导入错误,由 PyErr_Print() 打印如下:

stitches@Andromida:~$ sudo nautilus
retval= 0 user=stitches
Traceback (most recent call last):
  File "/usr/lib/python2.7/subho_auth.py", line 8, in <module>
    import numpy
  File "/usr/lib/python2.7/dist-packages/numpy/__init__.py", line 153, in <module>
    from . import add_newdocs
  File "/usr/lib/python2.7/dist-packages/numpy/add_newdocs.py", line 13, in <module>
    from numpy.lib import add_newdoc
  File "/usr/lib/python2.7/dist-packages/numpy/lib/__init__.py", line 8, in <module>
    from .type_check import *
  File "/usr/lib/python2.7/dist-packages/numpy/lib/type_check.py", line 11, in <module>
    import numpy.core.numeric as _nx
  File "/usr/lib/python2.7/dist-packages/numpy/core/__init__.py", line 6, in <module>
    from . import multiarray
ImportError: /usr/lib/python2.7/dist-packages/numpy/core/multiarray.so: undefined symbol: PyExc_SystemError
Segmentation fault (core dumped)

据我所知,它无法导入 test.py 文件中指定的 numpy 库。如何解决ImportError & PyExc_SystemError?

这个问题

如果我 运行 如下所示,python 脚本会发挥作用:

#include <Python.h>
#include <stdlib.h>
#include <string.h>
int main()
{   
    // Set PYTHONPATH TO working directory
    //int res = setenv("PYTHONPATH",".",1);
    //fprintf(stdout, "%d", res);

    PyObject *pName, *pModule, *pDict, *pFunc, *pValue, *pResult;

    // Initialize the Python Interpreter
    Py_Initialize();

    // Build the name object
    pName = PyString_FromString((char*)"test");

    // Load the module object
    pModule = PyImport_Import(pName);

    // pDict is a borrowed reference 

    PyErr_Print();
    pDict = PyModule_GetDict(pModule);

    // pFunc is also a borrowed reference 
    pFunc = PyDict_GetItemString(pDict, (char*)"test_func");

    if (PyCallable_Check(pFunc))
    {
        pValue=NULL;
        PyErr_Print();
        pResult=PyObject_CallObject(pFunc,pValue);
        PyErr_Print();
    }else 
    {
           PyErr_Print();
    }
    printf("Result is %s\n",PyString_AsString(pResult));

    // Clean up
    Py_DECREF(pModule);
    Py_DECREF(pName);/* */

    // Finish the Python Interpreter
    Py_Finalize();      

    return 0;
}

如果它在一般 python 嵌入示例下工作,为什么它在基于 PAM 的嵌入示例(使用 .so 文件的地方)下不起作用?

PS:出于特殊原因,我正在导入 numpy。不要问为什么我没有在 python 脚本中的任何地方使用,因为这只是我想要实现的演示脚本。此外,导入数学不会给出任何导入错误。我也收到 SciPY 的导入错误。

PPS:Numpy 和 Scipy 包在 python 脚本中完美运行,安装在 /usr/lib/python2.7/dist-packages/ 下。我正在使用 ubuntu 14.04.

请帮忙!!!!

我不知道你问题的答案,但我想知道为什么它没有早点失败。主机应用程序不知道您的 PAM 模块将需要使用 libpython2.7.so.1,因此必须以某种方式动态加载,否则 Py_Initialize() 调用将失败并显示同样的错误。

鉴于你说它不会在那里失败,它必须被加载。但是,从您收到的错误中,我们可以推断出它包含的符号(例如 PyExc_SystemError)对于随后加载的动态库不可见。这是使用 dlopen() 加载库时的默认设置(参见 RTLD_LOCAL in man 3 dlopen).要覆盖它,您必须将 RTLD_GLOBAL 传递给 dlopen()。也许这是你的问题。

关于您的代码的其他评论:

  • 为每个 pm_sm_...() 调用调用 Py_Initialise() 将是昂贵的并且可能会让 python 模块感到惊讶。这意味着 python 模块在一次调用中积累的所有数据(比如语音或用户名)将在下一次调用时被丢弃。您最好加载 libpython2.7.so.1 并初始化 PAM 一次,然后使用 pam_set_data() 的清理功能在完成后将其卸载。

  • 在相关问题中,您的 PAM 模块无法在 Python 程序中使用,因为您总是调用 Py_Initialise() (我假设匹配调用 Py_Finalize())。

  • 如果你的程序没有掉落在它掉落的地方,它就会掉落在 printf("Result is %s\n",PyString_AsString(pResult )) 因为 pResult 没有初始化。

  • 我想你知道,这里所有的样板都可以让你在 Python 中编写 PAM 模块,由 pam-python 提供——不需要 C。由于您显然是在 Python 中编写 PAM 模块,因此您已经暴露了它所产生的开销,但错过了它提供的功能,例如记录未捕获的 Python 异常。最重要的是,使用它意味着您可以完全避免使用 C。您的 PAM 模块将被加载到保护机器安全的程序中 - 像 login、sudo 和 xdm/gdm3 这样的程序。避免 C 也意味着避免 C 程序可能存在的大量安全漏洞,这些漏洞在 Python 中是不可能的——缓冲区溢出、未初始化的指针和访问释放的内存。由于您在此处发布的 C 代码中存在其中一个错误,因此避免它听起来是个好主意。