• The Old New Thing

    How do I launch a file as if it were a text file, even though its extension is not .txt?

    • 18 Comments

    You might have a program that generates log files or other text content with an extension other than .txt. You naturally might want to open these documents in the user's default text editor.

    You might decide to ask the Windows developer support team, "How can I figure out what program is the handler for text files?" The idea being that once you get this program name, you can then run it yourself, with the document on the command line. And you would also be running into the trap of looking for the answer to a question rather than a solution to a problem.

    For one thing, the default handler for the file type might require special command line parameters, parameters which you won't get if you merely get the executable path. For example, on Windows 7, the default command line for JPG files is %SystemRoot%\System32\rundll32.exe "%ProgramFiles%\Windows Photo Viewer\PhotoViewer.dll", ImageView_Fullscreen %1, and if you merely asked for the executable, all you would get back would be rundll32.exe, and trying to execute rundll32.exe Boats.jpg doesn't get you very far. You lost all the command line arguments.

    For another thing, the default handler for the file type might not even be a command line. It might be an old program that uses DDE. Or the handler might be a drop target. Or it could be an IContext­Menu or an IExecute­Command. In these cases, there is no command line in the first place, so asking for the command line template is meaningless.

    But we saw the answer to this question before, just in a different guise. The lpClass member of the SHELL­EXECUTE­INFO lets you open a file as if it were another type of file. In that article, somebody was passing a class when they didn't mean to; here, we're passing it on purpose.

  • The Old New Thing

    On nearly getting pickpocketed in both Lisbon and Madrid

    • 15 Comments

    My trip to Lisbon introduced me to another tourist phenomenon: pickpockets.

    It was around 10:30 in the morning, and I got on the train to head into town, planning to climb the steps through the Alfama district to visit the castle which looms over the city. The morning rush was over, and the Metro car was nearly empty.

    Just before the doors closed, a group of about four twentysomething guys stumbled onto the train, walking unsteadily and talking quite loudly among themselves. I found this immediately suspicious. They are acting drunk, but who is drunk at 10:30 in the morning? At 10:30, you're hung over, not drunk. And even if you are drunk, you are drunk in the comfort of your home, not stumbling around the subways.

    Even though the subway car had only about three people, and there was plenty of room to spread out, this group of pretend-drunks hung around close to me. I went on high alert.

    A few seconds later, one of the guys "stumbled" into me and thrust his hand into my pants pocket. I immediately grabbed his hand and yanked it back out, making sure he didn't get anything, adding a shout of "Hey!" (I don't speak Portuguese, so I couldn't say anything more eloquent.)

    Still keeping up the ruse of just being a bunch of loud-mouthed drunks, the group of would-be pickpockets stumbled off the train just as the doors closed. Well, three of them did. One of them didn't quite get off in time and stood with his face against the wall until the train reached its next stop, at which point he ran off.

    After Lisbon, I headed over to Madrid, and on the Metro I was on one of the long escalators connecting between two train lines, and I caught the person behind me surreptitously trying to unzip an outside pocket on my shoulder bag. He hadn't made much progress, but just to make sure he didn't get anything, I said hello and shook his hand.

    Bonus chatter: Getting targeted by thieves in Europe seems to be a tradition for me. During the Berlin phase of a previous visit to the continent, a thief tried unsuccessfully to steal the camera out of my hand. Sweden treats me well, though. I don't get targeted by pickpockets; just people trying to recruit me into some sort of organization.

    [Raymond is currently away, possibly being pickpocketed this very moment.]

  • The Old New Thing

    Understanding the classical model for linking: Sometimes you don't want a symbol to come along for a ride

    • 7 Comments
    Continuing our study of the classical model for linking, let's take another look at the trick of taking symbols along for the ride.

    The technique of taking symbols along for the ride is quite handy if that's what you want, but sometimes you don't actually want it. For example, a symbol taken along for the ride may create conflicts or create unwanted dependencies.

    Here's an example: Suppose you have a library called stuff.lib where you put functions that are used by various modules in different projects. One of the files in your library might look like this:

    // filedatestuff.cpp
    
    BOOL GetFileCreationTimeW(
            LPCWSTR pszFile,
            FILETIME *pft)
    {
        WIN32_FILE_ATTRIBUTE_DATA wfad;
        BOOL fSuccess = GetFileAttributesExW(pszFile,
                                 GetFileExInfoStandard,
                                 &wfad);
        if (fSuccess) {
            *pft = wfad.ftCreationTime;
        } else {
            pft->dwLowDateTime = 0;
            pft->dwHighDateTime = 0;
        }
        return fSuccess;
    }
    
    BOOL GetFileCreationTimeAsStringW(
             LPCWSTR pszFile,
             LPWSTR pszBuf,
             UINT cchBuf)
    {
        FILETIME ft;
        BOOL fSuccess = GetFileCreationTimeW(pszFile, &ft);
        if (fSuccess) {
            fSuccess = SHFormatDateTimeW(&ft, NULL,
                                         pszBuf, cchBuf) > 0;
        }
        return fSuccess;
    }
    

    Things are working out great, people like the helper functions in your library, and then you get a bug report:

    When my program calls the Get­File­Creation­TimeW function, I get a linker error: unresolved external: __imp__SHFormat­Date­TimeW. If I remove my call to Get­File­Creation­TimeW, then my program builds fine.

    You scratch your head. "The program is calling Get­File­Creation­TimeW, but that function doesn't call SHFormat­Date­TimeW, so why are we getting an unresolved external error? Any why hasn't anybody else run into this problem before?"

    First question first. Why are we getting an unresolved external error for a nonexistent external dependency?

    Because the Get­File­Creation­Time­As­StringW function got taken along for the ride. When the customer's program called Get­File­Creation­TimeW, that pulled in the filedatestuff.obj file, and that OBJ file contains both Get­File­Creation­TimeW and Get­File­Creation­Time­As­StringW. Since they are in the same OBJ file, pulling in one function pulls in all of them.

    The fix is to split the filedatastuff.cpp file into two files, one for each function. That way, when you pull in one function, nobody else comes along for the ride.

    Now to the second half of the question: Why did nobody run into this problem before?

    The Get­File­Creation­TimeW function has a dependency on Get­File­Attributes­ExW, which is a function in KERNEL32.DLL. On the other hand, the Get­File­Creation­Time­As­StringW function has a dependency on SHFormat­Date­TimeW, which is a function in SHLWAPI.DLL. If somebody lists KERNEL32.LIB as a dependent library in their project, but they don't include SHLWAPI.LIB on that list, then they will encounter this problem because the linker will pull in the reference to SHFormat­Date­TimeW and have no way of resolving it.

    Nobody ran into this before because SHLWAPI.LIB has lots of cute little functions in it, so most people include it in their project. Only if somebody is being frugal and leaving SHLWAPI.LIB out of their project will they run into this problem.

    Bonus chatter: The suggestion to split the file into two will work, but if you are really clever, you can still do some consolidation. Instead of splitting up files by functional group (for example, "all FILETIME functions"), you need to split them up based on their dependencies ("functions that are dependent solely on SHLWAPI.LIB"). Of course, this type of organization may make the code harder to follow ("Why did you put Get­File­Creation­Time­As­StringW and Hash­String in the same file?"), so you have to balance this against maintainability and readability. For example, somebody who is not aware of the classical model for linking may add a function to the file that has a dependency on SHELL32.DLL, and now your careful separation has fallen apart.

  • The Old New Thing

    For Honor, For Excellence, For Pizza

    • 18 Comments

    Hacker News member citizenlow recalls the time I went over after hours to help out the Money team debug a nasty kernel issue. They were running into mysterious crashes during their stress testing and asked for my help in debugging it.

    I helped out other teams quite a bit, like writing a new version of Dr. Watson for the Windows 98 team or writing a new version of the MSConfig tool based on a sketch on a bar napkin. And for a time, I followed the official policy for moonlighting to make sure everybody understood that I was doing work outside the boundaries of my official job duties.

    When the Money folks asked me for help, I told them that before I could help them, they would have to help me fill out some paperwork.

    • Who will you be working for? Microsoft Corporation.
    • Where will you be doing the work? Office XX/YYYY on Microsoft Redmond Campus.
    • When will the work begin and end? Begin on YYYY/MM/DD at 5pm, ending YYYY/MM/DD at 11pm.
    • How much will you be paid for this work?

    The Money folks were not sure how to answer that last question, since they didn't have any formal budget or procedures for hiring an outside consultant, much less any procedures for hiring one from inside the company.

    I told them, "Just write One slice of pizza."

    Nobody from the Personnel department seemed to notice the odd circumstances of this moonlighting request; they simply rubber-stamped it and put it in my file.

    The crash, it turns out, was in Windows itself. There was a bug in the special compiler the Languages team produced to help build certain components of Windows 95 which resulted in an incorrect address computation under a particularly convoluted boundary condition. The Money folks had merely stumbled across this bug as part of their regular testing. I notified the appropriate people, and the Windows team applied a workaround in their code to tickle the compiler into generating the correct code.

    As I recall, the pizza was just fine. It was just your average delivery pizza, nothing gourmet or anything. Not that it had to be, because I wasn't there for the pizza.

  • The Old New Thing

    Isn't every dinner at a technology conference a geek dinner?

    • 8 Comments

    I'm always amused when somebody announces that they're having a geek dinner at a technology conference. I mean, at a conference like that, every dinner is a geek dinner. The geek density is so high, +4 enchanted vorpal swords quiver in fear.

    [Update 8am: Fixed broken permalink. Yay, they broke a permalink...]

  • The Old New Thing

    How can I tell that I have a shell folder that represents My Computer?

    • 6 Comments

    You have in your hands an IShell­Folder, and you want to know whether this is an IShell­Folder that represents My Computer. There are a few ideas that may occur to you.

    One is to ask the folder for its current location and compare it to CSIDL_DRIVES.

    #define STRICT_TYPED_ITEMIDS
    #include <shlobj.h>
    
    PIDLIST_ABSOLUTE GetIDListViaPersistFolder(IUnknown *punk)
    {
      PIDLIST_ABSOLUTE pidl = NULL;
      IPersistFolder2 *ppf;
      if (SUCCEEDED(punk->QueryInterface(IID_PPV_ARGS(&ppf)))) {
        ppf->GetCurFolder(&pidl);
        ppf->Release();
      }
      return pidl;
    }
    
    HRESULT CompareAbsoluteIDLists(
        LPARAM lParam,
        PCUIDLIST_ABSOLUTE pidl1,
        PCUIDLIST_ABSOLUTE pidl2,
        int *piResult)
    {
      *piResult = 0;
      IShellFolder *psfDesktop;
      HRESULT hr = SHGetDesktopFolder(&psfDesktop);
      if (SUCCEEDED(hr)) {
        hr = psfDesktop->CompareIDs(lParam,
        reinterpret_cast<PCUIDLIST_RELATIVE>(pidl1),
        reinterpret_cast<PCUIDLIST_RELATIVE>(pidl2));
        if (SUCCEEDED(hr)) {
         *piResult = ShortFromResult(hr);
        }
        psfDesktop->Release();
      }
      return hr;
    }
    
    BOOL IsMyComputerFolder(IUnknown *punk)
    {
      BOOL fIsMyComputer = FALSE;
      PIDLIST_ABSOLUTE pidl = GetIDListViaPersistFolder(punk);
      if (pidl) {
        PIDLIST_ABSOLUTE pidlMyComputer;
        if (SUCCEEDED(SHGetSpecialFolderLocation(NULL,
                                     CSIDL_DRIVES, &pidlMyComputer)))
        {
          int iCompare;
          fIsMyComputer = SUCCEEDED(CompareAbsoluteIDLists(
                                   SHCIDS_CANONICALONLY,
                                   pidl, pidlMyComputer, &iCompare) &&
                          iCompare == 0;
          CoTaskMemFree(pidlMyComputer);
        }
        CoTaskMemFree(pidl);
      }
      return fIsMyComputer;
    }
    

    Okay, we have a lot of moving parts here. Let's look at them one at a time.

    The Get­IDList­Via­Persist­Folder function takes an object and asks IPersist­Folder2::Get­Cur­Folder what folder it represents. Since we don't actually use any methods on the object beyond what is provided by IUnknown, we weaken the parameter requirement to simply IUnknown.

    The Compare­Absolute­IDLists function compares two absolute ID lists according to the criteria specified by the lParam.

    The Is­My­Computer­Folder combines these two function: It takes the object you pass in and gets the ID list it represents. It then gets the ID list for the My Computer folder. And then it compares the two via SHCIDS_CANONICAL­ONLY, which means "I just want to see if they represent the same object. Don't worry about getting the sort order absolutely right." And again, since we don't use any methods on the object other than IUnknown::Query­Interface, we weaken the parameter requirements to simply IUnknown.

    Now, this code could be simplified or at least tweaked to take advantage of IShell­Item. For example, we could use SHGet­Known­Folder­Item to get the FOLDERID_Computer­Folder and then use IShell­Item::Compare.

    But I'm not going to bother, because there is an underlying algorithmic problem with this technique: It is checking whether you have a folder to My Computer specifically at its default location. If somebody creates a My Computer folder at a custom location, say via a folder shortcut, or a folder with a magic name, then this code will not recognize it as My Computer because these alternate locations for My Computer will not match the standard location.

    If you want to identify My Computer no matter where it winds up, then instead of checking the path, you can check its class.

    HRESULT GetObjectCLSID(IUnknown *punk, CLSID *pclsid)
    {
      *pclsid = CLSID_NULL;
      IPersist *pp;
      HRESULT hr = punk->QueryInterface(IID_PPV_ARGS(&pp));
      if (SUCCEEDED(hr)) {
        hr = pp->GetClassID(pclsid);
        pp->Release();
      }
      return hr;
    }
    
    BOOL IsMyComputerFolder(IUnknown *punk)
    {
      CLSID clsid;
      GetObjectCLSID(psf, &clsid);
      return clsid == CLSID_MyComputer;
    }
    

    We ask the object directly, "Hey, what's your CLSID?" and if it replies, "I am CLSID_My­Computer," then we say, "Pleased to meet you, My Computer."

  • The Old New Thing

    How does the C runtime know whether to use the static-linking or dynamic-linking version of the header file?

    • 14 Comments

    In response to a description of what happens when you get dll­import wrong, nksingh asks, "This seems like a problem for the CRT. As far as I know, VC gives you the option of statically or dynamically linking the CRT. But it seems like the headers will have to make a choice to support one thing better than the other. Conditional compilation would work, but then people would have to remember to include a #define somewhere. Is this dllimport vs. static linking thing something the compiler could figure out on its own if you're doing Link-time codegen?"

    Let's start from the beginning.

    Yes, this would be a problem for the CRT since it wouldn't know whether to declare the functions as normal static functions or as dllimport-style functions, and the headers have to make a choice which way to go.

    And if you look at the headers, you can see that it is indeed done via conditional compilation.

    ...
    _CRTIMP int __cdecl fflush(FILE * _File);
    ...
    

    This magic _CRTIMP symbol is defined in crtdefs.h like so:

    /* Define _CRTIMP */
    #ifndef _CRTIMP
    #ifdef _DLL
    #define _CRTIMP __declspec(dllimport)
    #else  /* _DLL */
    #define _CRTIMP
    #endif  /* _DLL */
    #endif  /* _CRTIMP */
    

    Conditional compilation decides whether _CRTIMP expands to __declspec(dllimport) or to nothing at all, depending on whether the _DLL symbol is defined.

    And yet nobody bothers writing #define _DLL before they #include <stdio.h>. There must be something else going on.

    In fact, we can run some experiments to see what's going on.

    #ifdef _DLL
    #error "_DLL is defined"
    #else
    #error "_DLL is not defined"
    #endif
    

    Save this as dummy.c and run a few tests.

    C:\tests> cl /MT dummy.c
    dummy.c
    dummy.c(4) : fatal error C1189: #error :  "_DLL is not defined"
    
    C:\tests> cl /MD dummy.c
    dummy.c
    dummy.c(2) : fatal error C1189: #error :  "_DLL is defined"
    

    Well how's about that. The compiler uses the /MT and /MD flag to decide whether or not to define the preprocessor symbol _DLL, which is the secret signal it passes to the crtdef.h header file to control the conditional compilation.

    The compiler has to use this technique instead of deferring the decision to link-time code generation because it cannot assume that everybody has enabled link-time code generation. (Indeed, we explicitly did not in our sample command lines.)

    If link-time code generation were enabled, then is this something that could be deferred until that point?

    In principle yes, because link-time code generation in theory could just make the .obj file a copy of the source file (and all the header files) and do all the actual compiling at link time. This is a sort of extreme way of doing it, but I guess it could've been done that way.

    On the other hand, it also means that the compiler folks would have to come up with a new nonstandard extension that means "This function might be a normal static function or it might be a dll­import function. I haven't decided yet; I'll tell you later."

    Seeing as how the CRT already has to solve the problem in the case where there is no link-time code generation, it doesn't seem worth the effort to add a feature to link-time-code generation that you don't actually need. It would be a feature for which the only client is the C runtime library itself, for which the C runtime library already requires a separate solution when link-time code generation is disabled, and for which that separate solution still works when link-time code generation is enabled.

    No engineering purpose is served by writing code just for the sake of writing code.

  • The Old New Thing

    Why don't elevated processes inherit their environment variables from their non-elevated parent?

    • 23 Comments

    As a general rule, child processes inherit the environment of their parent. But if the parent is non-elevated and the child is elevated, then this inheritance does not happen. Why not?

    There are two answers to this question. For the kernel-color glasses answer, I defer to Chris Jackson, the App Compat Guy. It's interesting to see how it all works, but it doesn't explain why the mechanism was designed to block environment variable inheritance.

    The reason for the design is that allowing an elevated process to inherit the PATH from a non-elevated process creates an attack vector.

    The non-elevated process sets its PATH to put some attacker-controlled directories ahead of the directories the elevated application actually expects. For example, suppose the elevated application links to C:\Program Files\Common Files\Contoso\Contoso­Grid­Control.dll. It arranges for this by setting the system PATH to include the C:\Program Files\Common Files\Contoso directory. Or maybe the program calls Load­Library on a DLL that might not exist, and it handles the case that the call fails by disabling some optional feature. (Whether this is a good idea or not is beside the point.)

    The attacker changes the PATH to read \\rogue\server;C:\Program Files\Common Files\Contoso, so that the library search finds the evil copy on the rogue server before finding the expected version in the Common Files directory (or in the case of a DLL that may not exist, it finds the evil copy on the rogue server instead of failing outright).

    Bingo, the attacker has injected arbitrary code into an elevated process. Game over.

    For similar reasons, the current directory is reset to the system directory when a non-elevated program launches an elevated program.

    If the environment and current directory were inherited, then malware could ask to elevate Program X with a custom current directory or environment. The user will merely be asked if they want to run Program X elevated, unaware that it is being run in a nonstandard manner, using an execution environment that did not receive administrator approval. As a result, the malware would be able to sneak into the administrator account under sheep's clothing (the sheep being Program X).

    What if you want to run another program elevated, and with a custom current directory or environment?

    Write a wrapper program which sets the current directory and environment, then launches the desired target process. Then ask the user for permission to run the wrapper elevated.

  • The Old New Thing

    Even your folder icons can be used as a Rorschach test

    • 19 Comments

    Jenny Lam (now at Jackson Fish Market) forwarded me this picture of a USB thumb drive. She also reminded me of another one of those Windows as Rorschach test incidents that surrounded the Windows Vista folder icons.

    It was reported during one of the betas that the 16×16 folder icon looked like someone flipping the bird. Sure, this interpretation required some creativity, and it perhaps reflects more on the person making the observation than on the folder icon itself, but the report still had to be taken seriously, because one thing you don't want is a newspaper headline saying that your product uses crudely offensive imagery. And yes, we went back and changed the icon to avoid the problem.

    Now, if you take that Folderix flash drive and tilt your head to the right about 60°, you can turn it into somebody giving you the finger with their right hand: The folder tab is the thumb, the folder body is the back of the hand, and the USB connector is the extended middle finger.

    Who knows, maybe that was intentional after all.

  • The Old New Thing

    When the option becomes so second-nature you forget that it's an option

    • 17 Comments

    A user of the imaginary Program Q program wanted to write an automated test that created a table, then ran various sub-test which communicated among each other by updating that table.

    When my test tries to create a table, the program asks the following question:

    q install server -r testdb
    
    Setting up this machine to be a registered table server...
    
    Registered table servers must adhere to Microsoft information 
    security policies. See http://programq/policy for details.
    If you have questions, contact mailto:qpolicy.
    
    Do you agree to adhere to Microsoft policies regarding
    registered table servers (y/n/q)?
    

    Is there a way to suppress the question? I can't pre-create a single server that all the tests connect to, because multiple tests running simultaneously would end up colliding with each other. I would prefer that each test run on its own isolated table server, but when I try to install a table server on the machine being tested, I get the above prompt.

    Why not just create an unregistered table server instead? Just leave off the -r flag. Give your problem description, there appears to be no need for the table server to be registered.
    Ah, didn't know about the ability to create an unregistered server. Works great!

    The user was apparently so accustomed to creating registered table servers that he didn't realize that there was any other kind. My guess is that he had no idea what the -r flag did; he just cargo-culted it from somewhere.

    Remember: The target audience for Program Q is not non-technical end-users. The target audience is other programmers, and this person was clearly a programmer since he was writing an automated test!

Page 377 of 433 (4,330 items) «375376377378379»