Jesses

集中精神
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

内容来自http://hi.baidu.com/zzzevazzz/blog/item/fa97bf163c0c2d59f2de32ef.html

 

关于PsLookupThreadByThreadId,MSDN里有这样一段描述:

A file system filter driver can enumerate active threads by calling PsLookupThreadByThreadId to convert a thread ID to an ETHREAD structure. The thread ID is available in the thread create routine. A file system filter driver can set a thread notification callback routine using PsSetCreateThreadNotifyRoutine. In the notification callback routine, the file system filter driver can use the passed in ThreadId parameter and call PsLookupThreadByThreadId to locate the ETHREAD structure.

听起来很不错,但实际却不行。这里微软犯了一个错误,从Win2000开始延续至今。

下面引用WRK的代码来说明错误的原因。

NTSTATUS
PsLookupThreadByThreadId(
    __in HANDLE ThreadId,
    __deref_out PETHREAD *Thread
    )
{

    PHANDLE_TABLE_ENTRY CidEntry;
    PETHREAD lThread;
    PETHREAD CurrentThread;
    NTSTATUS Status;

    PAGED_CODE();

    Status = STATUS_INVALID_PARAMETER;

    CurrentThread = PsGetCurrentThread ();
    KeEnterCriticalRegionThread (&CurrentThread->Tcb);

    CidEntry = ExMapHandleToPointer(PspCidTable, ThreadId);
    if (CidEntry != NULL) {
        lThread = (PETHREAD)CidEntry->Object;
        if (lThread->Tcb.Header.Type == ThreadObject && lThread->GrantedAccess) {

            if (ObReferenceObjectSafe(lThread)) {
                *Thread = lThread;
                Status = STATUS_SUCCESS;
            }
        }

        ExUnlockHandleTableEntry(PspCidTable, CidEntry);
    }

    KeLeaveCriticalRegionThread (&CurrentThread->Tcb);

    return Status;
}

通过跟踪PsLookupThreadByThreadId的调用过程发现,无法成功是因为lThread->GrantedAccess这个条件不满足。

再看PspCreateThread的代码。

NTSTATUS
PspCreateThread(
    OUT PHANDLE ThreadHandle,
    IN ACCESS_MASK DesiredAccess,
    IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
    IN HANDLE ProcessHandle,
    IN PEPROCESS ProcessPointer,
    OUT PCLIENT_ID ClientId OPTIONAL,
    IN PCONTEXT ThreadContext OPTIONAL,
    IN PINITIAL_TEB InitialTeb OPTIONAL,
    IN BOOLEAN CreateSuspended,
    IN PKSTART_ROUTINE StartRoutine OPTIONAL,
    IN PVOID StartContext
    )
{
    //
    // ...... 省略无关代码 ......
    //

    RtlZeroMemory (Thread, sizeof (ETHREAD));

    //
    // Initialize rundown protection for cross thread TEB refs etc.
    //
    ExInitializeRundownProtection (&Thread->RundownProtect);

    //
    // Assign this thread to the process so that from now on
    // we don't have to dereference in error paths.
    //
    Thread->ThreadsProcess = Process;

    Thread->Cid.UniqueProcess = Process->UniqueProcessId;

    CidEntry.Object = Thread;
    CidEntry.GrantedAccess = 0;
    Thread->Cid.UniqueThread = ExCreateHandle (PspCidTable, &CidEntry);

    if (Thread->Cid.UniqueThread == NULL) {
        ObDereferenceObject (Thread);
        return (STATUS_INSUFFICIENT_RESOURCES);
    }

    //
    // ...... 省略无关代码 ......
    //

    //
    // Notify registered callout routines of thread creation.
    //

    if (PspCreateThreadNotifyRoutineCount != 0) {
        ULONG i;
        PEX_CALLBACK_ROUTINE_BLOCK CallBack;
        PCREATE_THREAD_NOTIFY_ROUTINE Rtn;

        for (i = 0; i < PSP_MAX_CREATE_THREAD_NOTIFY; i++) {
            CallBack = ExReferenceCallBackBlock (&PspCreateThreadNotifyRoutine[i]);
            if (CallBack != NULL) {
                Rtn = (PCREATE_THREAD_NOTIFY_ROUTINE) ExGetCallBackBlockRoutine (CallBack);
                Rtn (Thread->Cid.UniqueProcess,
                     Thread->Cid.UniqueThread,
                     TRUE);
                ExDereferenceCallBackBlock (&PspCreateThreadNotifyRoutine[i],
                                            CallBack);
            }
        }
    }

    //
    // ...... 省略无关代码 ......
    //

    if ((Thread->CrossThreadFlags&PS_CROSS_THREAD_FLAGS_DEADTHREAD) == 0) {
        Status = ObGetObjectSecurity (Thread,
                                      &SecurityDescriptor,
                                      &MemoryAllocated);
        if (!NT_SUCCESS (Status)) {
            //
            // This trick us used so that Dbgk doesn't report
            // events for dead threads
            //
            PS_SET_BITS (&Thread->CrossThreadFlags,
                         PS_CROSS_THREAD_FLAGS_DEADTHREAD);

            if (CreateSuspended) {
                KeResumeThread(&Thread->Tcb);
            }
            KeReadyThread (&Thread->Tcb);
            ObDereferenceObject (Thread);
            ObCloseHandle (LocalThreadHandle, PreviousMode);
            return Status;
        }

        //
        // Compute the subject security context
        //

        SubjectContext.ProcessAuditId = Process;
        SubjectContext.PrimaryToken = PsReferencePrimaryToken(Process);
        SubjectContext.ClientToken = NULL;

        AccessCheck = SeAccessCheck (SecurityDescriptor,
                                     &SubjectContext,
                                     FALSE,
                                     MAXIMUM_ALLOWED,
                                     0,
                                     NULL,
                                     &PsThreadType->TypeInfo.GenericMapping,
                                     PreviousMode,
                                     &Thread->GrantedAccess,
                                     &accesst);

        PsDereferencePrimaryTokenEx (Process, SubjectContext.PrimaryToken);

        ObReleaseObjectSecurity (SecurityDescriptor,
                                 MemoryAllocated);

        if (!AccessCheck) {
            Thread->GrantedAccess = 0;
        }

        Thread->GrantedAccess |= (THREAD_TERMINATE | THREAD_SET_INFORMATION | THREAD_QUERY_INFORMATION);

    } else {
        Thread->GrantedAccess = THREAD_ALL_ACCESS;
    }

    KeReadyThread (&Thread->Tcb);
    ObDereferenceObject (Thread);

    return Status;
}

也就是说,Thread->GrantedAccess在线程创建通知之后才初始化,之前的值是0,所以前面说的条件无法满足。

从Win2000到Win2003,都存在这个问题。直到Vista,GrantedAccess的初始化才放到通知之前。

微软在更新文档的时候,显然忘记了测试旧的操作系统。

 

 

顺带一提,FsRtlCreateSectionForDataScan这个函数也存在一个问题。

函数原型如下;

NTSTATUS
FsRtlCreateSectionForDataScan(
    OUT PHANDLE SectionHandle,
    OUT PVOID *SectionObject,
    OUT PLARGE_INTEGER SectionFileSize OPTIONAL,
    IN PFILE_OBJECT FileObject,
    IN ACCESS_MASK DesiredAccess,
    IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
    IN PLARGE_INTEGER MaximumSize OPTIONAL,
    IN ULONG SectionPageProtection,
    IN ULONG AllocationAttributes,
    IN ULONG Flags
    );

问题在于ObjectAttributes参数居然依赖于PreviousMode。当PreviousMode==UserMode时,必须提供用户态地址。

要知道,FsRtlCreateSectionForDataScan并不是Nt/ZwXxx系统服务,而是内核专用的函数,常用于文件系统过滤驱动中,因此上下文是不确定的。这让使用者到哪里搞一个用户态地址的ObjectAttributes出来呢。临时分配就太慢了。而与此同时,SectionHandle,SectionFileSize等其他参数却总是内核地址。

对于既要处理用户态也要处理内核参数的函数,一般的做法是设一个ProbeMode参数,就像ObCreateObject那样。

出现问题的根本原因是,ObjectAttributes被直接传递给内部函数MmCreateSection。而MmCreateSection没有设计ProbeMode参数。

FsRtlCreateSectionForDataScan从2000sp4增量补丁包(Update Rollup)、XPsp2和2003sp1之后才出现,可以说是专为文件系统过滤驱动设计的。之前MmCreateSection的主要调用者只有NtCreateSection这个系统服务。所以MmCreateSection直接调用KeGetPreviousMode得到PreviousMode也没问题。不曾想后来FsRtlCreateSectionForDataScan第三者插足,导致了如今不和谐的状况。