microsoft/WSL

Provide a way to positively detect WSL from an app compiled on Linux.

dmiller-nmap opened this issue ยท 33 comments

I understand that support is always improving, but it seems unlikely that the specific network functionality (raw sockets, packet sniffing, netlink routes) required by Nmap will ever be supported in WSL/BashOnWindows. We would like to be able to detect WSL and provide users with a helpful message pointing them to our Windows-native build, while allowing our app to continue until the first crash/error.

Is there any stable, predictable method for detecting whether our built-on-Linux program is being run on Windows Subsystem for Linux?

Can't speculate on 'stable', but /proc/version has the string 'Microsoft' which seems a pretty safe bet.

@therealkenc For the time being this is probably the best way to do it. I can't promise that we'll never change the content of these ProcFs files, but I think it's unlikely we'll change it to something that doesn't contain "Microsoft" or "WSL".

/proc/sys/kernel/osrelease
/proc/version
fpqc commented

@benhillis Do you guys over at Microsoft think you might ever support any/most/all of this low-level functionality used by nmap, eventually, in the (distant) future? Are there reasons in principle why you wouldn't want to or couldn't realistically support them?

@fpqc There's a lot of surface areas and features to cover in limited time. The best way to help us prioritize work is by adding items to our User Voice page.

fpqc commented

@benhillis Yes absolutely you only have a limited time before the Anniversary update for Win10. I was just asking if the team over at Microsoft is planning on eventually seeing the project through to something on which the whole of the Linux userland can run (albeit maybe sometimes requiring tweaks). I mean, of course, Microsoft is a business, not a charity, and ultimately development will continue only if WSL meets business goals, but I was just asking whether the current plan is that development will continue by the WSL team improving compatibility for the foreseeable future.

I think WSL is really Windows 10's new "Killer App" (to use some cliched marketing lingo), and I personally know several people already who have moved (or are prepared to move) back to a Windows ecosystem from Linux because it's such an exciting project.

My biggest worry is that MS is going to take the project to something like 85% completion and then let it stagnate. WSL is something for which I would be willing to pay and for which my company would also be willing to pay, so I hope that development will continue past the anniversary release.

@fpqc This question is definitely above my pay-grade but I'll try to answer as best I can.

I will say that as a developer on this feature I'm excited about the future of the project don't see it stagnating any time soon. We have some exciting things planned that make me optimistic about the future of our technology.

Thank you for your kind words. I'm sorry if I'm being somewhat vague but I'm trying to err on the side of not putting my foot in my mouth :)

Closing this out since the discussion has run its course.

It would be great if someone would update config.guess to detect Ubuntu on Windows. Currently, Ubuntu and Ubuntu-on-Windows return identical results.

fpqc commented

@haydentech For now, it's better not to mainline patches to add WSL support, because it will start fudging tests that the devs would like to solve by improving their driver. The best way to test that is real-world usage, with software hitting all of the syscalls we would expect on a real Linux system. The team is planning to implement unimplemented syscalls and fix buggy ones.

It would be great if someone would update config.guess

Unfortunately that doesn't work, because the header files (both kernel and glibc) lie by design. "It's a feature". So, say you push a config.guess patch to savanah to return x86_64-pc-wslwin-gnu, and it gets accepted by the FSF. But, then what.

That won't (for example) make PulseAudio (#486) realize that PTHREAD_PRIO_INHERIT is defined in the pthread.h header, but said feature does not actually work on the platform. You are going to have to push a patch to PA's ax_pthread.m4 too. And if you are going to patch PA to teach it about pc-wslwin-gnu, well... might as well be checking /proc/version. No one pushes a compile-time platform check like that mind you, because Stephen is liable fix the thing without notice.

@therealkenc I don't expect a config.guess change to magically fix all problems resulting from differences in the 2 platforms. But it would fix my build problem, for one, and be a basis for future enhancements in other products. Right now, there is nothing to build on. Having config.guess return the same result for the two platforms is nonsensical.

I echo @fpqc's sentiment above

@fpqc - Regarding your comment, and specifically about raw sockets, the problem is that Windows does not natively support AF_PACKET socket domain. The functionality is exposed through other means on Windows because there are nmap tools on the internet that works on Windows. But, mapping AF_PACKET directly over that functionality is not feasible because of our commitment to compatibility. We are seriously discussing with the core networking team to light up these scenarios, now that it is pretty high on user voice (thanks for the feedback). As @benhillis mentions, some of these will take time given the surface area. Things are not out of reach technically, if there is demand.

@therealkenc ... Having config.guess return the same result for the two platforms is nonsensical.

That is one thesis. But it isn't me or Microsoft you have to convince. You need to convince the GNU maintainers to accept your patch that adds the new triplet.

uname -r | sed -n 's/.*\( *Microsoft *\).*/\1/p'

will output "Microsoft"
Guess that will be pretty unique for WSL :D

@benhillis I patched systemd to detect WSL1 as a container systemd/systemd#11932 . Now with WSL2 on the horizon I'm wondering how those two could be reliably detected as different environments? WSL2 seems to be closer to a VM, but it is expected to be different from a full Hyper-V VM.

With CMake, the check can be simply

if (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_HOST_SYSTEM_VERSION MATCHES "Microsoft")
  set(WSL True)
endif()

if (WSL)
  message("Doing WSL-specific stuff...")
endif()

uname -r | sed -n 's/.*\( *Microsoft *\).*/\1/p'

will output "Microsoft"

In my WSL2 Ubuntu image, it does not. I believe it might be because microsoft is lower-cased:

Adjusting the regex to include /i should work in either case:

$ uname -r | sed -n 's/.*\( *Microsoft *\).*/\1/ip'
microsoft

I do have the following environment variables present when I open a WSL terminal, though:

$ env | grep -i wsl
WSL_INTEROP=/run/WSL/18840_interop
WSL_DISTRO_NAME=Ubuntu
bluca commented

@rbalint @benhillis et al: I've just committed a change so that under WSL2 /proc/sys/kernel/osrelease will be of the form major.minor.patch-microsoft-WSL2-flavour, so for example:

4.19.112-microsoft-WSL2-standard

This should make it both deterministic and unique, and at the same time be compatible with the previous checks that looked for the "WSL" substring, like the systemd one.

The "microsoft" substring could appear in non-WSL2 kernel images as well, so it shouldn't be relied upon to find out if one is in WSL.

This should trickle down in the public at some point in the future - not really involved with that side of things so can't say for certain :-)

The "microsoft" substring could appear in non-WSL2 kernel images as well, so it shouldn't be relied upon to find out if one is in WSL.

This is what I was concerned about. Thank you for addressing it ๐Ÿ™Œ.

This should make it both deterministic and unique, and at the same time be compatible with the previous checks that looked for the "WSL" substring, like the systemd one.

Perfect!

@rbalint @benhillis et al: I've just committed a change so that under WSL2 /proc/sys/kernel/osrelease will be of the form major.minor.patch-microsoft-WSL2-flavour, so for example:

4.19.112-microsoft-WSL2-standard

This should make it both deterministic and unique, and at the same time be compatible with the previous checks that looked for the "WSL" substring, like the systemd one.

Looking at https://github.com/microsoft/WSL2-Linux-Kernel, I see that the change to include WSL2 was apparently applied just to the 5.4.y kernel branch (and version components are in a slightly different order: CONFIG_LOCALVERSION="-microsoft-standard-WSL2"), and the 4.19.y branch still has CONFIG_LOCALVERSION="-microsoft-standard".

The "microsoft" substring could appear in non-WSL2 kernel images as well, so it shouldn't be relied upon to find out if one is in WSL.

This should trickle down in the public at some point in the future - not really involved with that side of things so can't say for certain :-)

Recently WSL2 support was backported to Windows 10 versions 1903 and 1909, and apparently that backport still uses the 4.19.y kernel with the same -microsoft-standard version string which does not contain WSL2. If that backport would stay with the 4.19.y kernel branch for its lifetime, some solution for detecting those WSL2 environments would be needed.

I'm thinking about the following solution:

  1. If /proc/sys/kernel/osrelease contains either the capitalized Microsoft substring or the all-caps WSL substring, the environment is considered to be WSL without any further checks.
  2. If /proc/sys/kernel/osrelease contains the lowercase microsoft substring, this might be WSL2 with the 4.19.y kernel, or it might be something else from Microsoft, therefore some additional check is needed. What check could be used here? Initially I thought about testing whether the /run/WSL directory exists, but apparently that directory disappears when interop is turned off in /etc/wsl.conf, so it's not a 100% reliable test.

Could the existence of the /usr/sbin/wslpath symlink, and/or the success of something like wslpath -w /, be used as a fallback test for those old kernel versions? Is wslpath -w / guaranteed to succeed in the WSL environment?

Or maybe the first test after finding microsoft in osrelease would be to check for the /run/WSL directory; if it exists, consider the environment to be WSL; if it does not exist, try running wslpath (obviously I would want to avoid running external tools, but just the existence of /usr/sbin/wslpath might give a false positive if someone used WSL to prepare the system image and then just packed it into a tarball and placed into a different environment; /run is usually tmpfs, so should be safer against bringing its contents from another system).

@bluca Maybe there is another solution for detecting those 4.19.y-based WSL2 environments?

bluca commented

(and version components are in a slightly different order: CONFIG_LOCALVERSION="-microsoft-standard-WSL2"), and the 4.19.y branch still has CONFIG_LOCALVERSION="-microsoft-standard".

Yeah, some tools mistakenly took a dependency on the exact string "microsoft-standard" being there - they have been fixed since, but we swapped the order to try and keep backward compatibility.

Let me check where the 4.19.y branch is delivered from. If it's still released, then I can add the WSL2 suffix there too. I would advise against taking dependencies on anything else, as things like /run and /usr/sbin can and are manipulated by the distro of choice, so there's no guarantee.

What about /proc/sys/fs/binfmt_misc/WSLInterop? Looks like that name is hardcoded in /init (which is provided by the WSL subsystem, not by the distro), and the registration is performed even when interop is disabled. Although the distro can choose to unmount the binfmt_misc FS from there, this case could just be declared as unsupported (and if something decides to unregister that handler, interop will be broken for all concurrently running WSL2 distros, because they run as containers in a single shared Linux VM). What happens in some future WSL releases is also not really important, because that detection code would only be used when a 4.19.y-microsoft-standard kernel is encountered, and hopefully the kernel would be upgraded to something marked WSL2 before any significant changes in those places.

(Actually for my immediate use case even testing for /run/WSL could be enough, because a WSL environment with disabled interop won't work for me anyway โ€” the reason why I want to detect WSL is that I want to call Windows utilities which access USB devices in that case instead of trying to use equivalent Linux tools for that.)

the reason why I want to detect WSL is that I want to call Windows utilities which access USB devices in that case instead of trying to use equivalent Linux tools for that.)

In which case doing any kind of which lookup for some .exe like explorer.exe is probably sufficient for those purposes...

@bjeanes Thanks for reminding me about that very simple solution. Actually in some cases I already use powershell.exe (cmd.exe is useless because of its restriction for UNC paths), so I could probably just use that.

This method does not use external processes.
https://gist.github.com/abl/d004dcad84a16ddd670f5337ba5b896d

if [[ -f "/mnt/c/WINDOWS/system32/wsl.exe" ]]; then
  # We're in WSL, which defaults to umask 0 and causes issues with compaudit
  umask 0022
fi

if [[ -f "/mnt/c/WINDOWS/system32/wsl.exe" ]]; then

That method assumes that the 9p automount point hasn't be changed or turned off with wsl.conf. Use the osversion. There is a more recent landing zone at #4555.

What about checking for /etc/wsl.conf as a fallback if the directory /run/WSL doesn't exist? It sounds like you would need at least one of those two filesystem nodes.

Something like this bash code is what I have in mind:

if [[ -f "/run/WSL" ]] || [[ -f "/etc/wsl.conf" ]]; then
    echo "is wsl"
fi
bluca commented

WSL in /proc/sys/kernel/osrelease is supported everywhere and cannot be broken by the distro image, since it's provided by the kernel.

Since the distinction between WSL1 and WSL2 is that the first runs inside a container while the second runs in a virtual machine, we can make use of "systemd-detect-virt --container" to differentiate from both environments.

if [ -n "${WSL_DISTRO_NAME}" ]; then
  # In WSL but which one?
  virt_container="$(systemd-detect-virt --container)"
  case ${virt_container} in
    wsl)
      echo "This is WSL 1"
      ;;
    none)
      echo "This is WSL 2"
      ;;
    *)
      echo "Don't known ${virt_container}"
      ;;
  esac
fi

#423 (comment) remediated what this comment stated was problematic. Consequently, ignore it.

What about systemd-detect-virt?
Responds with wsl

@mlromramse, and returns none on bare metal. That's not bad.

PS /home/rokejulianlockhart> systemd-detect-virt
none
NativeCommandExitException: Program "systemd-detect-virt" ended with non-zero exit code: 1.
PS /home/rokejulianlockhart>