<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Soul Of Free Loop &#187; PAM</title>
	<atom:link href="https://zohead.com/archives/tag/pam/feed" rel="self" type="application/rss+xml" />
	<link>https://zohead.com</link>
	<description>Uranus Zhou&#039;s Blog</description>
	<lastBuildDate>Sat, 19 Jul 2025 15:42:46 +0000</lastBuildDate>
	<language>zh-CN</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.8</generator>
	<item>
		<title>Linux下出现 pam_get_item 的原因分析</title>
		<link>https://zohead.com/archives/linux-pam-get-item-error-library/</link>
		<comments>https://zohead.com/archives/linux-pam-get-item-error-library/#comments</comments>
		<pubDate>Wed, 11 Apr 2012 16:35:09 +0000</pubDate>
		<dc:creator><![CDATA[Uranus Zhou]]></dc:creator>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[dlopen]]></category>
		<category><![CDATA[PAM]]></category>
		<category><![CDATA[pam_get_item]]></category>
		<category><![CDATA[RTLD_GLOBAL]]></category>
		<category><![CDATA[编程]]></category>

		<guid isPermaLink="false">http://zohead.com/?p=90</guid>
		<description><![CDATA[本文博客链接：https://zohead.com/archives/linux-pam-get-item-error-library/ 近日在将一个 RHEL6 上的服务程序移植到老的 Fedora Core 2 Linux 上时出现了一些问题，该程序的功能为预先加载几个动态库，这几个动态库中再需要根据不同的需求做 PAM 用户验证操作，程序在 RHEL6 上编译和使用都没有问题，换到 Fedora Core 2 环境之后，编译时一切正常，但在运行时出现比较奇怪的 pam_get_item 错误： pam: PAM [dlerror: /lib/security/../../lib/secu [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>本文博客链接：<a href="https://zohead.com/archives/linux-pam-get-item-error-library/" target="_blank">https://zohead.com/archives/linux-pam-get-item-error-library/</a></p>
<p>	近日在将一个 RHEL6 上的服务程序移植到老的 Fedora Core 2 Linux 上时出现了一些问题，该程序的功能为预先加载几个动态库，这几个动态库中再需要根据不同的需求做 PAM 用户验证操作，程序在 RHEL6 上编译和使用都没有问题，换到 Fedora Core 2 环境之后，编译时一切正常，但在运行时出现比较奇怪的 pam_get_item 错误：</p>
<p>	<span style="color:#b22222;">pam: PAM [dlerror: /lib/security/../../lib/security/pam_env.so: undefined symbol: pam_get_item]<br />
	pam: PAM [dlerror: /lib/security/../../lib/security/pam_unix.so: undefined symbol: pam_get_item]<br />
	pam: PAM [dlerror: /lib/security/../../lib/security/pam_succeed_if.so: undefined symbol: pam_get_item]<br />
	...</span></p>
<p>	程序所加载的动态库中使用的 PAM 配置文件 <span style="color:#0000cd;">/etc/pam.d/system-auth</span> 里就用到了 <span style="color:#800080;">pam_env.so</span>、<span style="color:#800080;">pam_unix.so</span>、<span style="color:#800080;">pam_succeed_if.so</span> 等几个 PAM 模块。</p>
<p>	但实际系统中还有很多服务都使用 <span style="color:#0000cd;">/etc/pam.d/system-auth</span> 这个 PAM 配置来进行用户验证，都能正常工作，因此做下简单分析。</p>
<p>	由于此服务程序太复杂，就打定写个简单的模拟程序来进行分析。</p>
<p>	<strong>1、动态库部分（checkpam.c）：</strong></p>
<p>	PAM 验证的实现，调用 PAM 函数进行验证，具体 PAM 函数的调用就不详细写出了。</p>
<p><font color="#006400">#include &lt;stdio.h&gt;<br />
	#include &lt;stdlib.h&gt;<br />
	#include &lt;string.h&gt;<br />
	#include &lt;security/pam_appl.h&gt;</p>
<p>	int check_pam(const char * user, const char * pass)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; int ret = -1;</p>
<p>	&nbsp;&nbsp;&nbsp; pam_start(&quot;system-auth&quot;, user, ..., ...);<br />
	&nbsp;&nbsp;&nbsp; ...<br />
	&nbsp;&nbsp;&nbsp; // 实际 PAM 代码省略，成功返回0，失败返回1 //<br />
	&nbsp;&nbsp;&nbsp; ...<br />
	&nbsp;&nbsp;&nbsp; pam_end(...);</p>
<p>	&nbsp;&nbsp;&nbsp; return ret;<br />
	}</font></p>
<p>	和实际服务程序一致，通过 system-auth PAM 配置进行验证，编译生成动态库：</p>
<p>	cc -c checkpam.c<br />
	cc -shared -fPIC -lpam -o libcheckpam.so checkpam.o</p>
<p>	ldd libcheckpam.so，查看此动态库的依赖列表：</p>
<p>	<em>[root@linux root]# ldd libcheckpam.so<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; linux-gate.so.1 =&gt;&nbsp; (0x00c67000)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; libpam.so.0 =&gt; /lib/libpam.so.0 (0x0025b000)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; libc.so.6 =&gt; /lib/tls/libc.so.6 (0x0041d000)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; libdl.so.2 =&gt; /lib/libdl.so.2 (0x00153000)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /lib/ld-linux.so.2 =&gt; /lib/ld-linux.so.2 (0x00876000)</em><br />
	<strong><br />
	2、主程序部分（pamtest.c）：</strong></p>
<p>	加载动态库，调用 PAM 进行用户验证，比较丑陋，只需能验证，HOHO。</p>
<p><font color="#006400">#include &lt;stdio.h&gt;<br />
	#include &lt;stdlib.h&gt;<br />
	#include &lt;string.h&gt;<br />
	#include &lt;dlfcn.h&gt;</p>
<p>	int main(int argc, char ** argv)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; int ret = 0;<br />
	&nbsp;&nbsp;&nbsp; void * lpso = NULL;<br />
	&nbsp;&nbsp;&nbsp; int (* lpfunc)(const char *, const char *) = NULL;</p>
<p>	&nbsp;&nbsp;&nbsp; lpso = dlopen(&quot;./libcheckpam.so&quot;, RTLD_LAZY);<br />
	&nbsp;&nbsp;&nbsp; if (lpso == NULL) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; printf(&quot;Library load failed: %s.\n&quot;, dlerror());<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return 1;<br />
	&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; lpfunc = dlsym(lpso, &quot;check_pam&quot;);<br />
	&nbsp;&nbsp;&nbsp; if (lpfunc == NULL) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; printf(&quot;Load symbol failed: %s.\n&quot;, dlerror());<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; dlclose(lpso);<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return 2;<br />
	&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; ret = lpfunc(argv[1], argv[2], NULL);</p>
<p>	&nbsp;&nbsp;&nbsp; dlclose(lpso);</p>
<p>	&nbsp;&nbsp;&nbsp; printf(&quot;pam ret: %d.\n&quot;, ret);</p>
<p>	&nbsp;&nbsp;&nbsp; return ret;<br />
	}</font></p>
<p>	功能很简单，dlopen 加载刚才生成的 libcheckpam.so，然后 dlsym 找到 check_pam 函数，接着调用函数进行用户验证。</p>
<p>	编译程序：</p>
<p>	cc -o pamtest pamtest.c -ldl</p>
<p>	ldd pamtest，查看程序的依赖列表，可以看到此时已不需要 pam 库，和实际使用的服务程序的实现方式一致了：</p>
<p>	<em>[root@linux root]# ldd pamtest<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; linux-gate.so.1 =&gt;&nbsp; (0x00631000)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; libdl.so.2 =&gt; /lib/libdl.so.2 (0x009ac000)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; libc.so.6 =&gt; /lib/tls/libc.so.6 (0x0088f000)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /lib/ld-linux.so.2 =&gt; /lib/ld-linux.so.2 (0x00876000)</em></p>
<p>	然后运行 ./pamtest username password，参数分别指定用户名和密码，在 Fedora Core 2 上不管指定的用户名和密码是否正确，始终返回错误，并在系统日志中产生如文章开头所述的错误，而在 RHEL6 上就可以得到正确的结果。</p>
<p><span style="color:#f00;"><strong>原因分析：</strong></span></p>
<p>	经过查看 man 帮助并简单分析，终于找到原因，dlopen 的参数是这个问题的元凶之一。</p>
<p>	节选 man dlopen 的说明如下：</p>
<p>	The&nbsp; value&nbsp; of flag can be either RTLD_LAZY or RTLD_NOW. When RTLD_NOW is specified, or the environment variable LD_BIND_NOW is set to a&nbsp; nonempty&nbsp; string, all undefined symbols in the library are resolved before dlopen() returns. If this cannot be done, an error is returned.&nbsp; Otherwise binding is lazy: symbol values are first resolved when needed.</p>
<p>	Optionally,&nbsp; RTLD_GLOBAL&nbsp; may&nbsp; be&nbsp; or&rsquo;ed&nbsp; into&nbsp; flag, in which case the external symbols defined in the library will be made available for symbol&nbsp; resolution&nbsp; of&nbsp; subsequently&nbsp; loaded&nbsp; libraries. (The converse of RTLD_GLOBAL is RTLD_LOCAL. This is the default.)</p>
<p>	简单说就是如果指定了 RTLD_NOW 参数，在调用 dlopen 时就解析所有未定义符号，如果出错就 dlopen 失败，RTLD_LAZY 则不这样。<br />
	比较重要的是 RTLD_GLOBAL 参数，如果这个设置上，则 dlopen 加载的动态库的外部符号可以被后续加载的库解析到。</p>
<p>	关于库加载时的符号解析还有一段：</p>
<p>	External references in the library are resolved using the libraries&nbsp; in that&nbsp; library&rsquo;s&nbsp; dependency&nbsp; list&nbsp; and&nbsp; any&nbsp; other libraries previously opened with the RTLD_GLOBAL flag.&nbsp; If the executable&nbsp; was&nbsp; linked&nbsp; with the&nbsp; flag&nbsp; &quot;-rdynamic&quot; (or, synonymously, &quot;--export-dynamic&quot;), then the global symbols in the executable will also be used&nbsp; to&nbsp; resolve&nbsp; references in a dynamically loaded library.</p>
<p>	由于本例中没有使用 -rdynamic 参数，因此看前面一部分，库中的外部引用首先从库的依赖列表（也就是万能的 ldd 中的结果，哈哈）中查找，然后再从之前用了 RTLD_GLOBAL 参数加载的库中查找。</p>
<p>	具体到本例中，此测试程序使用的 dlopen 的参数为单独的 RTLD_LAZY，没有指定 RTLD_GLOBAL，PAM 验证时假设首先走 pam_env.so，这个 pam_env.so 也是在 pam_start、pam_end 等函数过程中通过 dlopen 动态加载的。</p>
<p>	虽然我们的 libcheckpam.so 动态库在编译时有依赖 libpam 库，但由于 pamtest 程序在 dlopen 时未指定 RTLD_GLOBAL，libpam 的符号不能被后续加载的 pam_env.so 等 PAM 库解析到，所以就出现文章开头的那些错误。</p>
<p>	<span style="color:#f00;"><strong>解决方法：</strong></span></p>
<p>	因此有几种方法让此程序能在 Fedora Core 2 系统中正常运行，以下三种都已经经过测试确认过：</p>
<p>	1、编译 pamtest 时指定依赖 pam 库，让 pam_env.so 等 PAM 库能解析到：<br />
	&nbsp;&nbsp;&nbsp; cc -o pamtest pamtest.c -ldl -lpam<br />
	2、加载 libcheckpam.so 时增加 RTLD_GLOBAL 参数，将 pamtest.c 中 dlopen 行改为：<br />
	&nbsp;&nbsp;&nbsp; lpso = dlopen(&quot;./libcheckpam.so&quot;, RTLD_LAZY | RTLD_GLOBAL);<br />
	3、如果碰到比较特殊的情况，pamtest 无法更改，就可以在运行 pamtest 前用 LD_PRELOAD 预先加载 libpam：<br />
	&nbsp;&nbsp;&nbsp; export LD_PRELOAD=&quot;/lib/libpam.so.0&quot;<br />
	&nbsp;&nbsp;&nbsp; ./pamtest username password</p>
<p>	另外提下为什么在 RHEL6 下没问题，Fedora Core 2 下有问题，看下两个系统下 pam_env.so 的依赖列表：</p>
<p>	<strong>Fedora Core 2 下</strong></p>
<p>	<em>[root@linux root]# ldd /lib/security/pam_env.so<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; linux-gate.so.1 =&gt;&nbsp; (0x00674000)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; libc.so.6 =&gt; /lib/tls/libc.so.6 (0x00111000)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /lib/ld-linux.so.2 =&gt; /lib/ld-linux.so.2 (0x00876000)</em></p>
<p>	<strong>RHEL6 下</strong></p>
<p>	<em>[root@localhost ~]# ldd /lib64/security/pam_env.so<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; linux-vdso.so.1 =&gt;&nbsp; (0x00007fff171ff000)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; libpam.so.0 =&gt; /lib64/libpam.so.0 (0x00007f6656472000)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; libaudit.so.1 =&gt; /lib64/libaudit.so.1 (0x00007f665625a000)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; libdl.so.2 =&gt; /lib64/libdl.so.2 (0x00007f6656056000)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; libcrypt.so.1 =&gt; /lib64/libcrypt.so.1 (0x00007f6655e1f000)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; libc.so.6 =&gt; /lib64/libc.so.6 (0x00007f6655a8d000)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /lib64/ld-linux-x86-64.so.2 (0x000000328c400000)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; libfreebl3.so =&gt; /lib64/libfreebl3.so (0x00007f665582b000)</em></p>
<p>	显然在 RHEL6 下，pam_env.so 本身就已经依赖了 libpam，这样 libpam 的符号肯定能被解析到，ldd 看看 /lib64/security/pam*.so 你会所有 PAM 动态库都已经依赖 libpam 库，这应该是新的系统中做的改动了。</p>
]]></content:encoded>
			<wfw:commentRss>https://zohead.com/archives/linux-pam-get-item-error-library/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
