If you use the eloquence64 #eloquence addon for the #NVDA#screenreader, a critical security release is now available. This is a critical update! Please update as soon as possible.
If you use Eloquence64 on secure screens, after the update, you must IMMEDIATELY! go to NVDA's settings dialogue, select eloquence from the list box, and press the "copy helper to system config" button again.
It fixes the following issues: * fix: eloquence64 now clears its log file on start * Fix: in some cases, eloquence64 could write to its log file at 30 megs per second * fix: in some cases, eloquence64 created multiple temp files and directories that could not be removed * fix: in some cases, eloquence64 could unexpectedly leave a port open on the computer * fix: the port and key to communicate with eloquence could be intercepted by other processes running on the machine * fix: in rare cases, it could have been possible for other processes on the user's machine to cause the addon to load and run arbitrary Python code by causing it to deserialize an attacker supplied pickle. If the addon was running on a secure screen, this would mean instant privilege escalation. * fixed: crashing or frozen NVDA will no longer leave orphaned eloquence processes behind * fix: more reliable pause and break lengths * security hardening: switch to named pipes with DACL for IPC, ensuring no other process can use the 32-bit host
@draeand In my case, I need something fast and built in. It really is the best fit. The issue here was using tcp for ipc. That was a poor decision that just happened rather than being intentionally considered.
@fastfinge I mean, you could use flat buffers or similar. That’s not built-in perhaps but it also doesn’t come with the risk of arbitrary RCE vulnerabilities in your code if you choose to use IPC mechanisms like this or to allow third parties to interact with serialized data. The downside is it’s an external dependency, but it’s insanely fast. Pickle is something you should only use if you have absolute control over the data that you unpickle.
@draeand Yes, and that was the problem. Third parties should not have been able to interact with the process that unpickles data. They could because I allowed things to happen as the code took shape over the past six years, and failed to review third partie PR's closely enough, rather than creating an intentional design for it. It then bit me when I forgot to correctly clean up the process that was listening to the TCP socket as part of other changes. The correct answer in this case was to move to named pipes, with process and group ased ACL, not dump pickle.
@draeand The ultimate solution is to rewrite the 32-bit eloquence host in something like rust, the way eloquence64RS does. But I don't speak or understand rust, so I'm not confident either writing it myself, or reviewing and sanity checking an AI generated rust host. Plus, NVDA itself, and the addon portion, are in Python. And I prefer to only require contributors (and me) to deal with a single programming language. Based on that round of bug-fixing, I'm already completely incapable of doing an adequate job. More complexity will not help me.
@fastfinge@draeand I get why you made the decisions you did, I really do. But I genuinely wonder whether rewriting the host in C# wouldn't be the right call for you. Yes, the contributors issue, but I'd ask the most regular ones if they'd be happy with such a move - you genuinely get a performance uplift doing it, for instance.
@nick@draeand I wouldn't, though. First the dotnet runtime would now be required, and would be a huge memory suck. Second the only lag Python brings is the first startup/JIT compile. Once that happens, it's just reading data off a pipe and passing it to eloquence. Getting rid of TCP saved me somewhere between 40 and 60 nanoseconds. But that's well below the limit of human perceivability. And generating NVDA indexes etc still happens in Python, because that's what NVDA uses. So a low level language just can't save anything here. The reason your rust driver feels more accurate is because you're playing back chunks sooner, and thus making indexing slightly less accurate in return. That's not a tradeoff I'd want to make. If I did, it could just as easily be made in Python.
@fastfinge@draeand Ah, I forgot about the .net runtime, so you'd get literally no RAM savings. Rust does get you those RAM savings, but I'll stand correct and admit that may be all it's giving me. Obviously, the main difference between our addons is that I'm willing to make that tradeoff in the name of responsiveness where you aren't, and that might be a big enough difference in daily use for both addons to need to exist.
@fastfinge@draeand As it happens, I do have one question. Have you tried the OpenEVV addon with Unspoken? Is it still early? Because that thing is just as quick as 64RS.
@nick@draeand It is. When the OpenEVV guys fix all the IBM introduced bugs, I intend to switch over to using there dlls, and just build those into the eloquence64 addon and get rid of IPC. Not doing IPC at all will get us a noticeable 40ms or so.
@fastfinge@nick@draeand I'm surprised that IPC adds double-digit milliseconds. The last time I measured IPC on Windows (specifically for the UI Automation API), inside a VM on a much older machine about 10 years ago, the round trip times were in the range of a few hundred microseconds.
@matt@nick@draeand In the real world, most machines have endpoint security and virus scanning and things that watch IPC carefully, and slow everything down. Especially when you're using TCP. Switching to pipes will probably bring some speedups for some folks, depending.
@matt@nick@draeand If you're curious, here's the biggest speedup I could get with half an hours work. I'm not going to even think about releasing this until I've been using it for a few days, though. It needs testing for clicks/underruns introduced by the much smaller audio buffer. github.com/fastfinge/eloquence_64/tree/perf/onedir-and-smaller-buffer
@fastfinge@matt@nick@draeand Are you saying that endpoint security adds 40 or more milliseconds of latency to each TCP packet? It seems a bit hard to believe, but Windows is the only OS on which the close syscall is slow and must often be relegated to a background thread, so what do I know. Or are you doing something interesting, like making a new connection on every utterance?
@miki@matt@nick@draeand Depends on the security software and the environment and the app. For an unsigned executable in an untrusted environment, running as admin, sometimes it can, yes. Of course this is not usually the case, so that kind of lag isn't going to hit most people, running most apps.
@fastfinge@matt@nick@draeand Unsigned executable. Yeah, that does make it a lot more believable. This community is just asking itself to get hacked in so many ways.
@fastfinge@matt@nick@miki You could use Azure artifact signing if you want a cert, it's only like $9.99 per month or so once you get past all the checks they do.
Re: performance. Try shared memory regions. DO NOT pass C++ objects across the boundary (again, this is where flatbuffers would be usful). Allocate a shared memory region with CreateFileMapping followed by MapViewOfFile, and maybe an event to signal the process when it needs to read from the region (you may need two events for this). That acts as an out-of-band signalling mechanism. Then performance is as fast as Windows can set events and as fast as your RAM is, provided you size the region big enough. I'm not sure what you submit to IPC so I don't know what that size would be, your best positioned to do that.
@matt@nick@draeand Now that you've got me curious, there are two places I can save. First, building the 32-bit hoast as onedir instead of onefile. Saves 1.5 seconds on first start. Second, I can shorten the audio buffer and save about 40 ms per speech. But that'll need a lot of testing to make sure it doesn't introduce underruns on slow systems.
@nick@draeand Just did some quick benchmarking. Based on the hotpath, a rewrite away from python could save as much as, get ready, 0.0005% of processing time.
@fastfinge@draeand LOL. ...Which honestly just means my experiment wasn't worth doing, except only insofar as it makes my experience using Windows on a Macbook Neo better as that's a very constrained environment. Honestly, when OpenEVV is actually ready, I'm getting out.
@nick One of these days I really do need to backport your dictionary handling code. But...that's HAAAARD WORK! wines I don't wanna! The dictionary code as it currently exists makes me sad and afraid.
@nick@fastfinge I honestly wonder if a C++ rewrite would be better. If the host only acts as a proxy to the ECI dll, then it should, in theory, be very simple to rewrite, although if you want serialization you'd need more deps (but most could be single-header).
@draeand@nick If we want to rewrite the world, the actually correct way to do this is write ECI64.dll, that's just a shim to call the 32-bit DLL, and then have anything at all that's a 64-bit process and wants to use eloquence load ECI64.dll. The primary reason I haven't done that is lack of talent. Though in hindsight, it probably would have actually been simpler than what I did wind up doing.
@fastfinge@nick You can't load a 32-bit DLL into a 64-bit address space though, which is the fundamental issue. I mean okay, I'll be a pedant and say that theoretically you could in fact load a 32-bit DLL into a 64-bit process, but it would be very very painful and require a bunch of low level hacks and black magic (and I'm pretty sure you would need to in effect be the library loader at that point). Not worth it unless you have a really really good reason or you like over-engineering things.
@draeand@nick No, but you could have ECI64.dll handle all of the IPC stuff, using what ever method you want, behind the scenes in a way that's largely invisible to the 64-bit app. And then every single app that wants to use eloquence doesn't have to do its own IPC.
@fastfinge@nick Very true, that's probably the best route unless you really want to learn how an operating system works. Which I would encourage, if only to learn new things!
@draeand@nick Hah. I'm a guy who forgot to clean up a process on close and left it that way for, like, months. I'm far away from doing low level OS work.
@fastfinge@nick Damnit, now the nerd side of me is working through actually making that work -- loading a 32-bit DLL into a 64-bit address space... Oooof
@draeand@nick If it wasn't for OpenEVV existing, and making all of this not matter within a year, I would probably write ECI64 in C and have it handle IPC with ECI32 via pipes or something. Then the three different eloquence addons can stop doing IPC, each in a slightly different way, and we can all focus on the actual differences and tradeoffs each one makes. Although, I don't think I can have a shared memory buffer between a 32 bit and a 64 bit process, can I? I still need pipes or something.