The per-class window styles and things really are per-class

Earlier, I discussed which window style bits belong to whom. One detail of this that I neglected to emphasize is that since the lower 16 bits of the window style are defined by the class, you can't just take styles from one class and apply them to another. For example, you can't create a button control and pass the SS_ENDELLIPSIS style expecting to have the text rendered with end ellipses. Because when you think you're passing SS_ENDELLIPSIS, you're really passing BS_NOTIFY:

#define SS_ENDELLIPSIS      0x00004000L
#define BS_NOTIFY           0x00004000L

The button control sees your 0x00004000L and treats it as BS_NOTIFY.

Remember that at the end of the day, window styles and window messages are just numbers. If you use a per-class window style or window message, you'd better be passing it to the class that defined it.

This also applies to window extra bytes. The value returned by GetWindowLongPtr(hwnd, DWLP_DLGPROC) is meaningful only if hwnd is a dialog box. I've seen code by a major commercial software manufacturer that just runs around fiddling with the DWLP_DLGPROC of every window on the desktop on the assumption that "Why of course it's a dialog box, why do you ask?" Well, except that DWLP_DLGPROC has the numerical value of 4 (or 8 on Win64). Positive window byte indices are class-defined. Asking for DWLP_DLGPROC of a random window will give you the dialog procedure if that window is a dialog box, but it'll return some other internal data if the window isn't. Fortunately, most window classes don't ask for more than sizeof(void*) extra bytes, so the request for DWLP_DLGPROC just fails with an invalid parameter error. But if there happened to be a window belonging to a class with a larger number of extra bytes, that window will be in for quite a surprise when that rogue program comes in and starts messing with those extra bytes.

Comments (13)
  1. jwf says:

    From a program isolation standpoint, why should another process be allowed to mess with my window’s extra bytes unless I enable such behavior?

    Virtual memory space is protected by default and that gives us much greater stability (shudder DOS days). Why not this sort of resource?

  2. Mihai says:

    I don’t think the two examples are in the same class.

    I agree with the first one. If I use a BS_* with a non-button, is wrong. The BS_ prefix and the doc spells it out.

    GetWindowLongPtr is working on any window. And a dialog is a special type a window, as is a button and so on.

    So I would expect that GetWindowLongPtr( DWLP_DLGPROC ) from a non-dialog will return zero ("I don’t have such a thing, because I am not a dialog, dummy!"). There are only 10 values, no overlapping, so there is no reason not to.

    The documentation has no warning on this, so I don’t think it is fair to balme the "major commercial software manufacturer"

  3. Dean Harding says:

    jwf: It’s one of Window’s principles that "everything on the desktop is equal" so to speak. That means you can move other windows, send messages to other windows and do pretty much anything to other windows that you like, as long as that window is on the same desktop as yours.

    This is regardless of which USER owns the particular windows, which is why checking the "Allow service to interact with desktop" checkbox on a service is such a bad idea.

  4. John Doe says:

    Re: Mihai

    The second parameter to GetWindowLongPtr is just an offset to the value you want. If you look up the definitions of GWL_EXSTYLE, etc. in the file WinUser.h you’ll see that those are just negative offsets.

    It therefore does not look like there is any "magic" involved. GetWindowLongPtr just adds the specified offset to some base address (defined by the hwnd I would guess) and returns whatever is stored there.

  5. SuperKoko says:

    quote from msdn (GetWindowLong): "The following values are also available when the hWnd parameter identifies a dialog box."

    It seems obvious that you cannot use these values if it is not a dialog box.

    Of course adding an explicit warning in the documentation would not harm. :)

    I have always thought that the dialog box system in Windows was written as a a high level layer above the lower-level window system. So, there should not be any special piece code for dialog boxes in non-dialog-boxes user32 functions. The fact that when we subclass a dialog box we must pass at least DLGWINDOWEXTRA bytes as cbWndExtra can also help someone to get an idea of what could be the implementation, and what he cannot assume.

    Of course, we cannot ask for all programmers to know the whole Win32 API and have thought of the phylosophy and deep meaning of each function before they write any piece of code.

    Maybe you noticed that MSDN’s documentation is improved each year. Warnings on some details that are not intuitive for everybody, are added.

  6. asdf says:

    jwf: to enable shatter attacks of course

  7. Mihai says:

    John Doe — I know they are offsets. But if I pass -5000 I expect an error (invalid parameter), not a pointer to something random. I don’t suspect it of magic, only of bad design.

    SuperKoko — You are right, the documentation spells it out. I have missed that.

    But I still think this is bad design.

    If the standard usage patern is:

    if( TestIfWndIsDialog(hWnd) )

    GetWindowLongPtr(hwnd, DWLP_DLGPROC)

    then I think the right thing is to move this inside the API. Especially that TestIfWndIsDialog will probably retrieve the class and compare it with "#32770", quite expensive compared with GetWindowLongPtr which probably has access to the windows internals and can just check a flag somewhere (I guess).

  8. You’re all missing the point. Look at the definition of DWLP_DLGPROC. Somebody else might have

    #define XWLP_IMAGEINDEX 4

    This is just like the

    #define SS_ENDELLIPSIS 0x00004000L

    #define BS_NOTIFY 0x00004000L

    conflict. The window manager can’t read your source code. It sees a 4. It doesn’t know whether you passed DWLP_DLGPROC or XWLP_IMAGEINDEX.

    If the test were moved into the API, you couldn’t use window extra bytes in your custom classes.

  9. Ulric says:

    Hey, is there an RSS feed to get all the comments of all posts as they come in?

  10. asdf says:

    It looks like you can actually sandbox your program from other apps (minus broadcast messages):

  11. Dean Harding says:

    asdf: The problem that article describes is a bug in the virus scanner – that is, it’s console runs as LocalSystem. Like I said, anything on the desktop is considered "equal" regardless of what user credentials it has. If everything on the desktop all had the same credentials, then that "flaw" would be meaningless because it wouldn’t let you do anything that you couldn’t already do.

    It all goes back to the days of Windows 3.x (or even Windows 9X) where everything really *was* equal.

  12. Dean Harding says:

    I don’t think the results of that report are any good. The problem is not that you sandbox your own application in a job, but that you have to sandbox the *malicious* program in a job with the right restrictions. And given that a process cannot be REassigned a job, all the malicious program has to do is assign itself to an unrestricted job before you get a chance to sandbox it.

  13. Norman Diamond says:

    The value returned by GetWindowLongPtr(hwnd,

    > DWLP_DLGPROC) is meaningful only if hwnd is

    > a dialog box.

    Or less. In Windows CE, according to MSDN, DWL_DLGPROC is stored in write-only memory! You can set it but you can’t get it.

Comments are closed.

Skip to main content